From 23e7b5a6b04f12d7429dd03b3f7723a2fc5c092f Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 28 Aug 2024 14:34:19 +0200 Subject: [PATCH] more api tests --- TODO | 6 +- api/api_v1.py | 50 ++++++-------- api/test_api_legacy.py | 13 ++++ api/test_api_v1.py | 142 ++++++++++++++++++++++++++++++++------- apps/classrooms/tests.py | 1 - apps/projects/models.py | 1 + 6 files changed, 156 insertions(+), 57 deletions(-) diff --git a/TODO b/TODO index f9d292a..049ec1d 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,12 @@ -* strip linebreaks from project names +* MIGRATION: + strip linebreaks and empty values from project names. + needs also fix for linked items (likes, comments) * more unit tests * tags suggestions? -* meta tags +* meta tags? more: diff --git a/api/api_v1.py b/api/api_v1.py index ec3c511..f117811 100644 --- a/api/api_v1.py +++ b/api/api_v1.py @@ -20,6 +20,8 @@ from ninja.errors import HttpError from allauth.account.forms import ResetPasswordForm from allauth.account.utils import send_email_confirmation +from allauth.account.forms import SignupForm +from allauth.account.adapter import DefaultAccountAdapter from apps.projects.models import Project from apps.classrooms.models import Group, SelectedProject @@ -33,25 +35,8 @@ ) # TODO -# * API rate lmiting/api/v1/projects/mash/test +# * API rate lmiting? -# TODO -# @api.post("/users/{username}?email=&password=&password_repeat=")) -# @api.post("/users/{username}/newpassword?oldpwassword=&password_repeat=&newpassword=") - - -# questions: -# - what is ?updatenotes (loading projects) XXX -# - what is ?persist -# - what is ?upublished - -# - what is "media" in snap-data / project file format - -# - password hashing? -# - why image in base64? - -# - rate limiting -# - securing emailing endpoints? ################################### @@ -173,12 +158,7 @@ def signup_user( try: user = User.objects.get(username=username) - print(user) - return 400, Errors( - errors=[ - f"User {username} already exists", - ] - ) + return 400, Errors(errors=[f"User {username} already exists",]) except User.DoesNotExist: pass @@ -188,6 +168,17 @@ def signup_user( except User.DoesNotExist: pass + # form = SignupForm( + # { + # "username": username, + # "email": email, + # "password1": password1, + # "password2": password2, + # }) + # if not form.is_valid(): + # return 400, Errors(errors=form.errors) + # form.save(request) + user = User(username=username, email=email, password=password) user.set_password(password) user.save() @@ -291,7 +282,8 @@ def logout_user(request): @api.get("/projects/{username}", response=ProjectList, summary="Get a user's projects.") def get_users_projects(request, username: str): - """ + """) + # for project in Get a user's projects. """ if request.user.is_authenticated and request.user.username == username: @@ -383,10 +375,11 @@ def save_project(request, username: str, projectname: str): notes = data["notes"] thumbnail = data["thumbnail"] soup = BeautifulSoup(contents, "xml") + # # we look for the first instances (less elegant but it might be nested) - thumbnail = ( - soup.find_all("pentrails")[0].text if soup.find_all("thumbnail") else None - ) + # thumbnail = soup.find_all("thumbnail")[0].text if soup.find_all("thumbnail") else thumbnail + thumbnail = soup.find_all("pentrails")[0].text if soup.find_all("pentrails") else thumbnail + # notes = soup.find_all("notes")[0].text if soup.find_all("notes") else None # # tag = soup.find_all("tags")[0].text if soup.find_all("notes") else None @@ -411,6 +404,7 @@ def save_project(request, username: str, projectname: str): project.thumbnail.save( f"{project.slug}.{ext}", ContentFile(base64.b64decode(imgstr)), save=True ) + project.save() if "group" in request.session: group = Group.objects.get(id=request.session["group"]) diff --git a/api/test_api_legacy.py b/api/test_api_legacy.py index b63e7e7..e4207c7 100644 --- a/api/test_api_legacy.py +++ b/api/test_api_legacy.py @@ -65,6 +65,13 @@ def test_set_project_visibility(self): self.assertEqual(response.status_code, 200) self.assertFalse(response.json()["ispublic"]) + self.client.logout() + response = self.client.get( + reverse("api-legacy:get_users_projects", args=["testuser"]) + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()), 0) + def test_delete_project(self): self.client.login(username="testuser", password="testpassword") response = self.client.get( @@ -87,3 +94,9 @@ def test_save_project(self): self.assertEqual( response.json()["contents"], "some notes #tagged" ) + + response = self.client.get( + reverse("api-legacy:get_users_projects", args=["testuser"]) + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()), 2) diff --git a/api/test_api_v1.py b/api/test_api_v1.py index 93fc33f..4fc4bca 100644 --- a/api/test_api_v1.py +++ b/api/test_api_v1.py @@ -1,39 +1,129 @@ +import json +import base64 from django.test import TestCase, Client -from django.urls import reverse -from apps.users.models import User +from django.contrib.auth import get_user_model +from django.core.files.uploadedfile import SimpleUploadedFile from apps.projects.models import Project +from apps.classrooms.models import Group, SelectedProject +from allauth.account.models import EmailAddress +User = get_user_model() - -class APITest(TestCase): +class APITestCase(TestCase): def setUp(self): - self.client = Client() - self.user = User.objects.create_user( - username="testuser", password="testpassword" - ) - self.project = Project.objects.create( - user=self.user, name="testproject", slug="testproject", notes="testproject" - ) + self.user = User.objects.create_user(username='testuser', email='test@example.com', password='snap$testpass') + self.user_email = EmailAddress.objects.create(user=self.user, email='test@example.com', primary=True, verified=True) + self.user2 = User.objects.create_user(username='testuse2r', email='test@example.com', password='snap$testpass') + self.project = Project.objects.create(user=self.user, name='testproject', is_public=True) + + def test_init(self): + response = self.client.post('/api/v1/init') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {}) def test_current_user(self): - response = self.client.get(reverse("api-v1:current_user")) + self.client.login(username='testuser', password='snap$testpass') + response = self.client.get('/api/v1/users/c') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()['username'], 'testuser') + + def test_login_user(self): + response = self.client.post('/api/v1/users/testuser/login', data='testpass', content_type='text/plain') + self.assertEqual(response.status_code, 200) + self.assertIn('logged in', response.json()['message']) + + def test_signup_user(self): + response = self.client.post('/api/v1/users/newuser?email=newuser@example.com&password=newpass&password_repeat=newpass', + content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertIn('Account created', response.json()['title']) + + response = self.client.post('/api/v1/users/newuser/login', data='newpass', content_type='text/plain') + self.assertEqual(response.status_code, 200) + self.assertIn('logged in', response.json()['message']) + + + def test_resend_verification(self): + response = self.client.get('/api/v1/users/testuser/resendverification') + self.assertEqual(response.status_code, 200) + + def test_request_password_change(self): + response = self.client.post('/api/v1/users/testuser/password_reset') + self.assertEqual(response.status_code, 200) + self.assertIn('password reset link', response.json()['message']) + + def test_change_password(self): + self.client.login(username='testuser', password='snap$testpass') + response = self.client.post('/api/v1/users/testuser/newpassword?oldpassword=testpass&newpassword=newpass&password_repeat=newpass', content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertIn('Password changed', response.json()['title']) + + def test_logout_user(self): + self.client.login(username='testuser', password='snap$testpass') + response = self.client.post('/api/v1/logout') self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["username"], "") - self.client.login(username="testuser", password="testpassword") - # response = self.client.get("/api/v1/users/c") - response = self.client.get(reverse("api-v1:current_user")) + self.assertEqual(response.json()['redirect'], '/') + + def test_save_load_delete_project(self): + self.client.login(username='testuser', password='snap$testpass') + data = { + 'xml': 'a test notedata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACqElEQVR4Xu3XMY7aUABF0e99QA/LAYkN0bAgEKuBjUBBRqSYYmaSKIWvZB3Xlp58/xHG0+vjGi4FogITgFF5s+8CAIKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0…7">', + 'notes': 'a test notes', + 'thumbnail': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACqElEQVR4Xu3XMY7aUABF0e99QA/LAYkN0bAgEKuBjUBBRqSYYmaSKIWvZB3Xlp58/xHG0+vjGi4FogITgFF5s+8CAIKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAJjmNw4gA2kBANP8xgFkIC0AYJrfOIAMpAUATPMbB5CBtACAaX7jADKQFgAwzW8cQAbSAgCm+Y0DyEBaAMA0v3EAGUgLAPhD/u12O47H4zgcDukBLX0cwD+c8H6/H6/X633H7XYb1+t1rNfrpZuY9fkA/Evu3W735Y5pmsbz+Rzn83nWw1riGID/CfDxeIzL5bJEE7M+E4D/+Aq+3+/vV/BqtZr1gJY+BuAPJ7zZbMbpdBrfvYKXjmLO5wNwztq2vv6f/vjK+/2Z51IgKOAXMIhu8rMAgDSkBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNbxxABtICAKb5jQPIQFoAwDS/cQAZSAsAmOY3DiADaQEA0/zGAWQgLQBgmt84gAykBQBM8xsHkIG0AIBpfuMAMpAWADDNb/wXK14Ct+2fpIIAAAAASUVORK5CYII=' + } + response = self.client.post('/api/v1/projects/testuser/newproject', data=json.dumps(data), content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertIn('project newproject saved', response.json()['message']) + + response = self.client.get('/api/v1/projects/testuser') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()['projects']), 2) + + response = self.client.get('/api/v1/projects/testuser/newproject/thumbnail') self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["username"], "testuser") + self.assertIn('data:image/png;base64,', response.content.decode()) - def test_logout(self): - self.client.login(username="testuser", password="testpassword") - response = self.client.post(reverse("api-v1:logout")) + response = self.client.delete('/api/v1/projects/testuser/newproject') self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["redirect"], "/") + self.assertIn('project newproject has been deleted', response.json()['message']) def test_get_users_projects(self): - self.client.login(username="testuser", password="testpassword") - response = self.client.get( - reverse("api-v1:get_users_projects", args=["testuser"]) - ) + self.client.login(username='testuser', password='snap$testpass') + response = self.client.get('/api/v1/projects/testuser') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()['projects']), 1) + + # def test_get_project_thumbnail(self): + # response = self.client.get('/api/v1/projects/testuser/newproject/thumbnail') + # self.assertEqual(response.status_code, 200) + # self.assertIn('data:image/png;base64,', response.content.decode()) + + # def test_get_project(self): + # response = self.client.get('/api/v1/projects/testuser/newproject') + # self.assertEqual(response.status_code, 200) + # self.assertIn('', response.content.decode()) + + def test_get_project_versions(self): + response = self.client.get('/api/v1/projects/testuser/testproject/versions') self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.json()["projects"]), 1) + self.assertEqual(response.json(), []) + + def test_set_project_visibility(self): + self.client.login(username='testuser', password='snap$testpass') + response = self.client.post('/api/v1/projects/testuser/testproject/metadata?ispublic=false&ispublished=false') + self.assertEqual(response.status_code, 200) + self.assertIn('project testproject updated', response.json()['message']) + + response = self.client.post('/api/v1/logout') + self.assertEqual(response.status_code, 200) + + self.client.login(username='testuser2', password='snap$testpass') + response = self.client.get('/api/v1/projects/testuser') + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()['projects']), 0) + + def test_delete_project(self): + self.client.login(username='testuser', password='snap$testpass') + response = self.client.delete('/api/v1/projects/testuser/testproject') + self.assertEqual(response.status_code, 200) + self.assertIn('project testproject has been deleted', response.json()['message']) + diff --git a/apps/classrooms/tests.py b/apps/classrooms/tests.py index 5a9597d..3e8db5f 100644 --- a/apps/classrooms/tests.py +++ b/apps/classrooms/tests.py @@ -83,7 +83,6 @@ def test_leave_view(self): self.assertEqual(response.status_code, 302) # Check for redirect self.assertFalse(self.group.members.filter(username='member').exists()) # Check if member left - def tearDown(self): User.objects.all().delete() Group.objects.all().delete() diff --git a/apps/projects/models.py b/apps/projects/models.py index ddd4455..b772168 100644 --- a/apps/projects/models.py +++ b/apps/projects/models.py @@ -61,6 +61,7 @@ def thumbs_upload_handler(instance, filename): notes = models.TextField( blank=True, null=True, + default="", verbose_name="Project notes", help_text="You can add hashtags here!", )