diff --git a/backend/secfit/comments/views.py b/backend/secfit/comments/views.py index b74d0f208c9bcf06ee49817541d47742767f0b7d..bb37fb1b432be7fb4908b103f6a34ef8c14f26c7 100644 --- a/backend/secfit/comments/views.py +++ b/backend/secfit/comments/views.py @@ -1,18 +1,15 @@ -from django.shortcuts import render -from rest_framework import generics, mixins +from django.db.models import Q +from rest_framework import generics, mixins, permissions +from rest_framework.filters import OrderingFilter from comments.models import Comment, Like -from rest_framework import permissions from comments.permissions import IsCommentVisibleToUser -from workouts.permissions import IsOwner, IsReadOnly from comments.serializers import CommentSerializer, LikeSerializer -from django.db.models import Q -from rest_framework.filters import OrderingFilter +from workouts.permissions import IsOwner, IsReadOnly # Create your views here. class CommentList( mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView ): - # queryset = Comment.objects.all() serializer_class = CommentSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [OrderingFilter] @@ -29,12 +26,13 @@ class CommentList( def get_queryset(self): workout_pk = self.kwargs.get("pk") - qs = Comment.objects.none() + query_set = Comment.objects.none() if workout_pk: - qs = Comment.objects.filter(workout=workout_pk) + query_set = Comment.objects.filter(workout=workout_pk) elif self.request.user: - """A comment should be visible to the requesting user if any of the following hold: + """ + A comment should be visible to the requesting user if any of the following hold: - The comment is on a public visibility workout - The comment was written by the user - The comment is on a coach visibility workout and the user is the workout owner's coach @@ -43,8 +41,7 @@ class CommentList( # The code below is kind of duplicate of the one in ./permissions.py # We should replace it with a better solution. # Or maybe not. - - qs = Comment.objects.filter( + query_set = Comment.objects.filter( Q(workout__visibility="PU") | Q(owner=self.request.user) | ( @@ -54,7 +51,7 @@ class CommentList( | Q(workout__owner=self.request.user) ).distinct() - return qs + return query_set # Details of comment class CommentDetail( diff --git a/backend/secfit/users/admin.py b/backend/secfit/users/admin.py index fc0af23c4473e29bcc06045aebfdd0d21989d22d..9c410c881a8cf639b4ebf7edaf114d03a8c39780 100644 --- a/backend/secfit/users/admin.py +++ b/backend/secfit/users/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin +from django.contrib.auth import get_user_model from django.contrib.auth.admin import UserAdmin from .models import Offer, AthleteFile -from django.contrib.auth import get_user_model from .forms import CustomUserChangeForm, CustomUserCreationForm # Register your models here. @@ -11,7 +11,6 @@ class CustomUserAdmin(UserAdmin): add_form = CustomUserCreationForm form = CustomUserChangeForm model = get_user_model() - # list_display = UserAdmin.list_display + ('coach',) fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("coach",)}),) add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ("coach",)}),) diff --git a/backend/secfit/users/serializers.py b/backend/secfit/users/serializers.py index e46a4c5084f2b380ba32728c6a458eeafcf6af0f..d159be0e1b887ad74e854e4741d71ec7eb9aff34 100644 --- a/backend/secfit/users/serializers.py +++ b/backend/secfit/users/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers +from django import forms from django.contrib.auth import get_user_model, password_validation from users.models import Offer, AthleteFile -from django import forms class UserSerializer(serializers.HyperlinkedModelSerializer): @@ -36,6 +36,9 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): password = data.get("password") password1 = data.get("password1") + if password != password1: + raise serializers.ValidationError("The passwords must match") + try: password_validation.validate_password(password) except forms.ValidationError as error: @@ -53,7 +56,16 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): street_address = validated_data["street_address"] favourite_exercise = validated_data["favourite_exercise"] main_gym = validated_data["main_gym"] - user_obj = get_user_model()(username=username, email=email, phone_number=phone_number, country=country, city=city, street_address=street_address, favourite_exercise=favourite_exercise, main_gym=main_gym) + user_obj = get_user_model()( + username=username, + email=email, + phone_number=phone_number, + country=country, + city=city, + street_address=street_address, + favourite_exercise=favourite_exercise, + main_gym=main_gym + ) user_obj.set_password(password) user_obj.save() diff --git a/backend/secfit/users/tests/test_serializers.py b/backend/secfit/users/tests/test_serializers.py index 0f5b2d17fc2319a048bdcca78570800e0339460f..327f1b3e64c9f58fe96e6c87ed2d365ae58a43e6 100644 --- a/backend/secfit/users/tests/test_serializers.py +++ b/backend/secfit/users/tests/test_serializers.py @@ -1,20 +1,23 @@ -from threading import local -from django.test import TestCase, RequestFactory, Client -from users.models import User +from django.test import TestCase from users.serializers import UserSerializer +from rest_framework import serializers # Create your tests here. class UserSerializerTestCase(TestCase): - # def setUp(self): - #anything? def test_validate_password(self): - print("User tests") my_pass = "pw" local_seri = UserSerializer(data={"password": my_pass, "password1": my_pass}) self.assertEqual(local_seri.validate_password(my_pass), my_pass) + def test_invalid_password(self): + my_pass = "pw" + invalid_pass = "hehe" + + local_seri = UserSerializer(data={"password": my_pass, "password1": invalid_pass}) + self.assertRaises(serializers.ValidationError, local_seri.validate_password, my_pass) + def test_create(self): username = "jorsi" email = "some@mail.com" diff --git a/backend/secfit/users/urls.py b/backend/secfit/users/urls.py index 282ffd7c338b96a1161f7e361a2c981fce6780f2..00dfc4c35a5647a98e78f6b1e78eaf7b7948d67e 100644 --- a/backend/secfit/users/urls.py +++ b/backend/secfit/users/urls.py @@ -1,6 +1,5 @@ -from django.urls import path, include +from django.urls import path from users import views -from rest_framework.urlpatterns import format_suffix_patterns urlpatterns = [ path("api/users/", views.UserList.as_view(), name="user-list"), diff --git a/backend/secfit/users/views.py b/backend/secfit/users/views.py index 245b1c2277e3b250e20afb16b5a4b7436b46d388..761c889c6c3ae6d64d086a993b6e95862c7762f6 100644 --- a/backend/secfit/users/views.py +++ b/backend/secfit/users/views.py @@ -1,7 +1,8 @@ -import django -from rest_framework import mixins, generics -from workouts.mixins import CreateListModelMixin -from rest_framework import permissions +from rest_framework import mixins, generics, permissions +from rest_framework.permissions import ( + IsAuthenticatedOrReadOnly, +) +from rest_framework.parsers import MultiPartParser, FormParser from users.serializers import ( UserSerializer, OfferSerializer, @@ -10,19 +11,12 @@ from users.serializers import ( UserGetSerializer, UserProfilePostSerializer ) -from rest_framework.permissions import ( - AllowAny, - IsAdminUser, - IsAuthenticated, - IsAuthenticatedOrReadOnly, -) from users.models import Offer, AthleteFile -from django.contrib.auth import get_user_model -from django.db.models import Q -from django.shortcuts import get_object_or_404 -from rest_framework.parsers import MultiPartParser, FormParser from users.permissions import IsCurrentUser, IsAthlete, IsCoach +from workouts.mixins import CreateListModelMixin from workouts.permissions import IsOwner, IsReadOnly +from django.contrib.auth import get_user_model +from django.db.models import Q # Create your views here. class UserList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): @@ -103,33 +97,31 @@ class OfferList( serializer.save(owner=self.request.user) def get_queryset(self): - qs = Offer.objects.none() result = Offer.objects.none() if self.request.user: - qs = Offer.objects.filter( + query_set = Offer.objects.filter( Q(owner=self.request.user) | Q(recipient=self.request.user) ).distinct() - qp = self.request.query_params - u = self.request.user + query_param = self.request.query_params + user = self.request.user # filtering by status (if provided) - s = qp.get("status", None) - if s is not None and self.request is not None: - qs = qs.filter(status=s) - if qp.get("status", None) is None: - qs = Offer.objects.filter(Q(owner=u)).distinct() + filter_status = query_param.get("status", None) + if filter_status is not None and self.request is not None: + query_set = query_set.filter(status=filter_status) + if query_param.get("status", None) is None: + query_set = Offer.objects.filter(Q(owner=user)).distinct() # filtering by category (sent or received) - c = qp.get("category", None) - if c is not None and qp is not None: - if c == "sent": - qs = qs.filter(owner=u) - elif c == "received": - qs = qs.filter(recipient=u) - return qs - else: - return result + filter_category = query_param.get("category", None) + if filter_category is not None and query_param is not None: + if filter_category == "sent": + query_set = query_set.filter(owner=user) + elif filter_category == "received": + query_set = query_set.filter(recipient=user) + return query_set + return result class OfferDetail( diff --git a/backend/secfit/workouts/models.py b/backend/secfit/workouts/models.py index 3148639aad3f65def0b2d043062f11ef8f9cb76f..0bd31bd4a853f17ca34da70d9f3c10a83b8f1fe6 100644 --- a/backend/secfit/workouts/models.py +++ b/backend/secfit/workouts/models.py @@ -65,7 +65,7 @@ class Workout(models.Model): ordering = ["-date"] def __str__(self): - return self.name + return str(self.name) class Exercise(models.Model): @@ -90,7 +90,7 @@ class Exercise(models.Model): unit = models.CharField(max_length=50) def __str__(self): - return self.name + return str(self.name) class Goal(models.Model): """Django model for an goal type that users can create. @@ -108,7 +108,7 @@ class Goal(models.Model): exercise = models.TextField() def __str__(self): - return self.name + return str(self.name) class ExerciseInstance(models.Model): @@ -174,4 +174,4 @@ class RememberMe(models.Model): remember_me = models.CharField(max_length=500) def __str__(self): - return self.remember_me + return str(self.remember_me) diff --git a/backend/secfit/workouts/serializers.py b/backend/secfit/workouts/serializers.py index 98036eec0661f5b05201221987788c30cf6b96eb..c533c14d1503141117307be92b2403d79f9cda9f 100644 --- a/backend/secfit/workouts/serializers.py +++ b/backend/secfit/workouts/serializers.py @@ -162,29 +162,32 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer): for i in range(len(exercise_instances_data), len(exercise_instances.all())): exercise_instances.all()[i].delete() - # Handle WorkoutFiles if "files" in validated_data: - files_data = validated_data.pop("files") - files = instance.files + self.handle_workout_files(instance, validated_data) - for file, file_data in zip(files.all(), files_data): - file.file = file_data.get("file", file.file) + return instance + + # Handle WorkoutFiles + def handle_workout_files(self, instance, validated_data): + files_data = validated_data.pop("files") + files = instance.files + + for file, file_data in zip(files.all(), files_data): + file.file = file_data.get("file", file.file) # If new files have been added, creating new WorkoutFiles - if len(files_data) > len(files.all()): - for i in range(len(files.all()), len(files_data)): - WorkoutFile.objects.create( + if len(files_data) > len(files.all()): + for i in range(len(files.all()), len(files_data)): + WorkoutFile.objects.create( workout=instance, owner=instance.owner, file=files_data[i].get("file"), ) # Else if files have been removed, delete WorkoutFiles - elif len(files_data) < len(files.all()): - for i in range(len(files_data), len(files.all())): - files.all()[i].delete() - - return instance + elif len(files_data) < len(files.all()): + for i in range(len(files_data), len(files.all())): + files.all()[i].delete() def get_owner_username(self, obj): """Returns the owning user's username @@ -213,7 +216,10 @@ class ExerciseSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Exercise - fields = ["url", "id", "name", "description", "duration", "calories", "muscleGroup", "unit", "instances"] + fields = [ + "url", "id", "name", "description", "duration", + "calories", "muscleGroup", "unit", "instances" + ] class GoalSerializer(serializers.HyperlinkedModelSerializer): """Serializer for an Goal. Hyperlinks are used for relationships by default. diff --git a/backend/secfit/workouts/views.py b/backend/secfit/workouts/views.py index 9b64e09924f9f43b86271b758cc2829205dd3af9..5a3750901693e37782a69715b17f5ebae62c9289 100644 --- a/backend/secfit/workouts/views.py +++ b/backend/secfit/workouts/views.py @@ -1,16 +1,16 @@ """Contains views for the workouts application. These are mostly class-based views. """ -from rest_framework import generics, mixins -from rest_framework import permissions - +from collections import namedtuple +import base64 +import pickle +from rest_framework import generics, mixins, permissions, filters from rest_framework.parsers import ( JSONParser, ) from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse -from django.db.models import Q -from rest_framework import filters +from rest_framework_simplejwt.tokens import RefreshToken from workouts.parsers import MultipartJsonParser from workouts.permissions import ( IsOwner, @@ -26,12 +26,8 @@ from workouts.models import Workout, Exercise, ExerciseInstance, Goal, WorkoutFi from workouts.serializers import WorkoutSerializer, ExerciseSerializer, GoalSerializer from workouts.serializers import RememberMeSerializer from workouts.serializers import ExerciseInstanceSerializer, WorkoutFileSerializer +from django.db.models import Q from django.core.exceptions import PermissionDenied -from rest_framework_simplejwt.tokens import RefreshToken -from rest_framework.response import Response -import json -from collections import namedtuple -import base64, pickle from django.core.signing import Signer @@ -66,16 +62,15 @@ class RememberMe( serializer_class = RememberMeSerializer def get(self, request): - if request.user.is_authenticated == False: + if not request.user.is_authenticated: raise PermissionDenied - else: - return Response({"remember_me": self.rememberme()}) + return Response({"remember_me": self.rememberme()}) def post(self, request): - cookieObject = namedtuple("Cookies", request.COOKIES.keys())( + coockie_object = namedtuple("Cookies", request.COOKIES.keys())( *request.COOKIES.values() ) - user = self.get_user(cookieObject) + user = self.get_user(coockie_object) refresh = RefreshToken.for_user(user) return Response( { @@ -84,13 +79,14 @@ class RememberMe( } ) - def get_user(self, cookieObject): - decode = base64.b64decode(cookieObject.remember_me) + def get_user(self, coockie_object): + decode = base64.b64decode(coockie_object.remember_me) user, sign = pickle.loads(decode) # Validate signature if sign == self.sign_user(user): return user + return None def rememberme(self): creds = [self.request.user, self.sign_user(str(self.request.user))] @@ -132,18 +128,18 @@ class WorkoutList( serializer.save(owner=self.request.user) def get_queryset(self): - qs = Workout.objects.none() + query_set = Workout.objects.none() if self.request.user: # A workout should be visible to the requesting user if any of the following hold: # - The workout has public visibility # - The owner of the workout is the requesting user # - The workout has coach visibility and the requesting user is the owner's coach - qs = Workout.objects.filter( + query_set = Workout.objects.filter( Q(visibility="PU") | (Q(visibility="CO") & Q(owner__coach=self.request.user)) ).distinct() - return qs + return query_set class WorkoutDetail( @@ -288,9 +284,9 @@ class ExerciseInstanceList( return self.create(request, *args, **kwargs) def get_queryset(self): - qs = ExerciseInstance.objects.none() + query_set = ExerciseInstance.objects.none() if self.request.user: - qs = ExerciseInstance.objects.filter( + query_set = ExerciseInstance.objects.filter( Q(workout__owner=self.request.user) | ( (Q(workout__visibility="CO") | Q(workout__visibility="PU")) @@ -298,7 +294,7 @@ class ExerciseInstanceList( ) ).distinct() - return qs + return query_set class ExerciseInstanceDetail( @@ -351,9 +347,9 @@ class WorkoutFileList( serializer.save(owner=self.request.user) def get_queryset(self): - qs = WorkoutFile.objects.none() + query_set = WorkoutFile.objects.none() if self.request.user: - qs = WorkoutFile.objects.filter( + query_set = WorkoutFile.objects.filter( Q(owner=self.request.user) | Q(workout__owner=self.request.user) | ( @@ -362,7 +358,7 @@ class WorkoutFileList( ) ).distinct() - return qs + return query_set class WorkoutFileDetail( diff --git a/frontend/cypress/integration/FR5.spec.js b/frontend/cypress/integration/FR5.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4e185811d09f6e32c326b0a1794bbf3b857ec0ab --- /dev/null +++ b/frontend/cypress/integration/FR5.spec.js @@ -0,0 +1,62 @@ +var baseUrl = "localhost:9090" + +describe('Black box testing', () => { + function logWorkout(workoutname, publicbool){ + cy.get("#btn-create-workout").click(); + cy.get('input[name="name"]').type(workoutname); + if(publicbool){ + cy.get("#inputVisibility").select("Public"); + }else{ + cy.get("#inputVisibility").select("Private") + } + cy.get('input[name="date"]').click().then(input => { + input[0].dispatchEvent(new Event('input', { bubbles: true })) + input.val('2017-04-30T13:00') + }).click() + cy.get('#inputNotes').type('This is a note') + cy.get("#btn-ok-workout").click(); + } + + + it('Logg personal workout and check if added in my list', function() { + this.login(this.athleteUser) + cy.get('#list-my-workouts-list').click(); + cy.get('h5').contains('Public Workout'); + }) + + it('Logg public personal and check if added', function() { + this.login(this.athleteUser) + cy.get('#list-public-workouts-list').click(); + cy.get('h5').contains('Public Workout'); + }) + + it('Logg public and check if added', function() { + this.login(this.athleteUser) + logWorkout("Public Workout", true); + cy.url().should('include', "/workouts.html"); + cy.get('h5').contains('Public Workout') + }) + + + it('See details of public workout', function() { + this.login(this.athleteUser) + cy.get('#list-public-workouts-list').click(); + cy.get('h5').contains('Public Workout').parent().click(); + cy.get('#inputName').should('be.visible'); + cy.get('#inputNotes').should('be.visible'); + cy.get('#customFile').should('be.visible'); + }) + + it('User should not be able to see other private workouts', function() { + this.login(this.athleteUser) + logWorkout("Private Workout", false); + cy.get('#btn-logout', { timeout: 10000 }).click() + cy.wait(4000) + this.login(this.coachUser) + cy.wait(2000) + cy.get('h5').contains('Private Workout').should('not.exist'); + cy.get('#list-public-workouts-list').click(); + cy.get('h5').contains('Private Workout').should('not.exist'); + }) + +}) diff --git a/frontend/cypress/integration/exercise.spec.js b/frontend/cypress/integration/exercise.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..136247a409c30b8d5c62b8e38ac03e12e15f7b3b --- /dev/null +++ b/frontend/cypress/integration/exercise.spec.js @@ -0,0 +1,22 @@ +describe('Create exercise', () => { + it('Exercise creation', function() { + this.login(this.athleteUser) + cy.get('#nav-exercises').click() + cy.get('#btn-create-exercise').click() + cy.url().should('include', 'exercise.html') + + cy.get('#inputName', { timeout: 10000 }).type('Workout') + cy.get('#inputDescription').type('Description') + cy.get('#inputUnit').type('10') + cy.get('#inputDuration').type("-10") + cy.get('#inputCalories').type("-10") + cy.get('select[name="muscleGroup"]').eq(0).select('Legs', {force: true}) + + cy.intercept({method: 'POST', url: '/api/exercises/' }).as('createRequest') + cy.get('#btn-ok-exercise').click() + cy.wait('@createRequest') + + cy.get('.alert').should('be.visible'); + }) + +}) diff --git a/frontend/cypress/integration/first.spec.js b/frontend/cypress/integration/first.spec.js deleted file mode 100644 index 80d873078d5c1eed7bad1abc1f31b22ef05c79f6..0000000000000000000000000000000000000000 --- a/frontend/cypress/integration/first.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -describe('Create exercise', () => { - it('Exercise creation', function() { - this.login(this.athleteUser) - cy.get('#nav-exercises').click() - cy.get('#btn-create-exercise').click() - cy.url().should('include', 'exercise.html') -// - typeIn( - 'Workout', - 'Description', - '10', - '-10', - '-10', - 'Legs', - ) - cy.intercept({method: 'POST', url: '/api/exercises/' }).as('createRequest') - cy.get('#btn-ok-exercise').click() - cy.wait('@createRequest') - // Successfully created exercise - cy.url().should('include', 'exercises.html') - }) - - - function typeIn(name, description, unit, duration, calories, muscleGroup) { - cy.get('#inputName', { timeout: 10000 }).type(name) - cy.get('#inputDescription').type(description) - cy.get('#inputUnit').type(unit) - cy.get('#inputDuration').type(duration) - cy.get('#inputCalories').type(calories) - cy.get('select[name="muscleGroup"]').eq(0).select(muscleGroup, {force: true}) - } - -}) diff --git a/frontend/cypress/integration/goals.spec.js b/frontend/cypress/integration/goals.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e3307c75d858e571ad2bff340d649bf934b28688 --- /dev/null +++ b/frontend/cypress/integration/goals.spec.js @@ -0,0 +1,73 @@ +const getDate = () => { + let d = new Date(); + const offset = d.getTimezoneOffset(); + d = new Date(d.getTime() - (offset*60*1000)); + return d.toISOString().split('T')[0]; +} + +// + +describe('Check goals', () => { + it('Create goal', function() { + this.login(this.athleteUser) + cy.get('#nav-goals').click() + cy.get('#btn-create-goal').click() + cy.url().should('include', 'goal.html') + + cy.get('#inputName', { timeout: 10000 }).type("GOAL_NAME") + cy.get('#inputDescription').type("GOAL_DESCRIPTION") + + cy.get('#inputDateTime') + .click() + .then(input => { + input[0].dispatchEvent(new Event('input', { bubbles: true })) + input.val('2017-04-30T13:00') + }) + .click() + // cy.intercept({method: 'POST', url: '/api/goal/' }).as('createRequest') + cy.get('#btn-ok-goal').click() + cy.wait(1000) + + cy.get('h2').eq(0).contains("GOAL_NAME") + //cy.wait('@createRequest') + cy.url().should('include', 'goals.html') + }) + + it('Edit goal', function() { + this.login(this.athleteUser) + cy.get('#nav-goals').click() + cy.get('.card-link') + .eq(0).click() + + cy.get('#inputName', { timeout: 10000 }).type("_EDITED") + + + + //cy.intercept({method: 'DELETE', url: '/api/goal/1/' }).as('createRequest') + cy.get('#btn-ok-goal').click() + cy.wait(1000) + + cy.get('h2').eq(0).contains("GOAL_NAME_EDITED") + + }) + + + it('Delete goal', function() { + this.login(this.athleteUser) + cy.get('#nav-goals').click() + cy.get('.card-link') + .eq(0).click() + + //cy.intercept({method: 'GET', url: '/api/goal/1/' }).as('createRequest') + //cy.wait('@createRequest') + cy.wait(1000) + + + //cy.intercept({method: 'DELETE', url: '/api/goal/1/' }).as('createRequest') + cy.get('#btn-delete-goal').click() + cy.wait(1000) + //cy.wait('@createRequest') + + cy.url().should('include', 'goals.html') + }) +}) diff --git a/frontend/cypress/integration/profile.spec.js b/frontend/cypress/integration/profile.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..761aad7d9aed86bd64fa84127e13bb47c80bef00 --- /dev/null +++ b/frontend/cypress/integration/profile.spec.js @@ -0,0 +1,35 @@ + + +describe('Test profile page', () => { + + it('Edit profile', function() { + this.login(this.athleteUser) + + cy.get('#nav-profile').click() + cy.get('#main_gym').type("SiT Moholt") + cy.get('#favourite_exercise').type("Cardio") + cy.get('#btn-edit-account').click() + cy.wait(1000) + cy.url().should('include', 'profile.html') + + cy.get('#main_gym').should('have.value', 'SiT Moholt'); + cy.get('#favourite_exercise').should('have.value', 'Cardio'); + }) + + + it('Connect to coach and see if the coach is added in profile', function() { + this.login(this.athleteUser) + + cy.get('#nav-mycoach').click() + cy.get('#input-coach').type(`${this.coachUser.username}`, {force: true}) + + cy.get('#button-edit-coach').click() + cy.get('#button-set-coach').click({force: true}) + cy.wait(2000) + + cy.get('#nav-profile').click() + cy.url().should('include', 'profile.html') + cy.get('#coach-content').contains(`${this.coachUser.username}`) + }) + +}) diff --git a/frontend/cypress/integration/register.spec.js b/frontend/cypress/integration/register.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d7eddbc72e02273210eac1211caae5df8e94ef8d --- /dev/null +++ b/frontend/cypress/integration/register.spec.js @@ -0,0 +1,57 @@ + +const randomUsername = () => { + return Math.random().toString(36).substring(7); +} +var baseUrl = "localhost:9090" +/** + * The only validation done in the frontend is thorugh email + */ +const HundredAndFiftyTwo = "tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 tdt4242 "; + +describe('boundary tests registration page', () => { + beforeEach(() => { + cy.visit(`${baseUrl}/register.html`) + }); + + it('Test lower boundary limit', () => { + cy.get('#btn-create-account').click() + + cy.get('.alert').should('be.visible'); + cy.get('.alert').get('li').contains('username').children('ul').children('li').contains('This field may not be blank.') + cy.get('li').contains('password').children('ul').children('li').contains('This field may not be blank.') + cy.get('li').contains('password1').children('ul').children('li').contains('This field may not be blank.') + + }); + + it('Test upper boundary limit', () => { + + cy.get('input[name="username"]').type(HundredAndFiftyTwo) + cy.get('input[name="email"]').type(HundredAndFiftyTwo) + cy.get('input[name="password"]').type(HundredAndFiftyTwo) + cy.get('input[name="password1"]').type(HundredAndFiftyTwo) + cy.get('input[name="phone_number"]').type(HundredAndFiftyTwo) + cy.get('input[name="country"]').type(HundredAndFiftyTwo) + cy.get('input[name="city"]').type(HundredAndFiftyTwo) + cy.get('input[name="street_address"]').type(HundredAndFiftyTwo) + cy.get('input[name="main_gym"]').type(HundredAndFiftyTwo) + cy.get('input[name="favourite_exercise"]').type(HundredAndFiftyTwo) + + + cy.get('#btn-create-account').click() + cy.get('.alert').should('be.visible'); + cy.get('li').contains('Enter a valid username. This value may contain only letters, numbers, and @/./+/-/_ characters.') + cy.get('li').contains('Ensure this field has no more than 150 characters.') + cy.get('li').contains('Enter a valid email address.') + cy.get('li').contains('phone_number').children('ul').children('li').contains("Ensure this field has no more than 50 characters.") + cy.get('li').contains('country').children('ul').children('li').contains("Ensure this field has no more than 50 characters.") + cy.get('li').contains('city').children('ul').children('li').contains("Ensure this field has no more than 50 characters.") + cy.get('li').contains('street_address').children('ul').children('li').contains("Ensure this field has no more than 50 characters.") + cy.get('li').contains('main_gym').children('ul').children('li').contains("Ensure this field has no more than 50 characters.") + cy.get('li').contains('favourite_exercise').children('ul').children('li').contains("Ensure this field has no more than 50 characters.") + + }); + + + +}); + diff --git a/frontend/cypress/support/index.js b/frontend/cypress/support/index.js index e62e3c9aced19397650cf3b8c9733ee459cbe111..d63cbb61f5aefa07a13fc97a9819d486aa39d044 100644 --- a/frontend/cypress/support/index.js +++ b/frontend/cypress/support/index.js @@ -38,18 +38,21 @@ var athleteUser = { var apiUrl = "localhost:8000" var baseUrl = "localhost:9090" -function getRandomString(len=40) { - const dec2hex = dec => dec.toString(16).padStart(2, "0") - - var arr = new Uint8Array(len / 2) - window.crypto.getRandomValues(arr) - return Array.from(arr, dec2hex).join('') +export function makeid(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for ( var i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * + charactersLength)); + } + return result; } function generateUser() { return { - username: `test_user_${getRandomString(10)}`, - password: getRandomString(10), + username: `test_user_${makeid(10)}`, + password: makeid(10), } } diff --git a/frontend/www/goals.html b/frontend/www/goals.html index 5acb9e39511c0b52446837014a04fa7c9a18a116..b56e6085b8cc2705f1788cca5b24151daae80ce8 100644 --- a/frontend/www/goals.html +++ b/frontend/www/goals.html @@ -25,7 +25,7 @@ - <template id="template-exercise"> + <template id="template-goals"> <div class="card ml-1" style="width: 24rem;"> <div class="card-body"> <h2 class="card-title" ></h2> diff --git a/frontend/www/scripts/defaults.js b/frontend/www/scripts/defaults.js index f09e5b11770e94dd97921c59f0f18f6d5176d863..5adc962b90cfec8c01ec41e7846ad8a40ffa50fc 100644 --- a/frontend/www/scripts/defaults.js +++ b/frontend/www/scripts/defaults.js @@ -1 +1 @@ -const HOST = "http://localhost:8000"; +const HOST = "http://localhost:9090"; diff --git a/frontend/www/scripts/goal.js b/frontend/www/scripts/goal.js index 14afd3289c8ac038cc6a4ada76f8089e0a601c2a..bf3ec898afb4494a730248ec0067e61191270d73 100644 --- a/frontend/www/scripts/goal.js +++ b/frontend/www/scripts/goal.js @@ -33,6 +33,7 @@ async function createGoal() { async function deleteExercise(id) { let response = await sendRequest("DELETE", `${HOST}/api/goal/${id}/`); + console.log(`${HOST}/api/goal/${id}/`) if (!response.ok) { let data = await response.json(); let alert = createAlert(`Could not delete goal ${id}`, data); diff --git a/frontend/www/scripts/goals.js b/frontend/www/scripts/goals.js index a4b86cc19629535d37859e457532b6db47194b62..a01bfdbfc98b2267f77ecf05fdf0f741e0c06daa 100644 --- a/frontend/www/scripts/goals.js +++ b/frontend/www/scripts/goals.js @@ -6,7 +6,7 @@ async function fetchExerciseTypes(request) { let goals = data.results; let container = document.getElementById('div-content'); - let exerciseTemplate = document.querySelector("#template-exercise"); + let exerciseTemplate = document.querySelector("#template-goals"); goals.forEach(goal => { const exerciseAnchor = exerciseTemplate.content.firstElementChild.cloneNode(true); exerciseAnchor.href = `goal.html?id=${goal.id}`;