Fakultas Ilmu Komputer UI

Commit 7218737a authored by Rahmania Astrid Mochtar's avatar Rahmania Astrid Mochtar
Browse files

Testinggoat/ch19 1

parent 37c2743b
......@@ -197,4 +197,13 @@ Hasil dari mutation testing
- timeout: 0 (0.0%)
```
Karena mutation score dari test adalah 100%, maka tidak perlu menambahkan test case baru untuk menambahkan kualitas.
\ No newline at end of file
Karena mutation score dari test adalah 100%, maka tidak perlu menambahkan test case baru untuk menambahkan kualitas.
## Exercise 8
### Manual Mocking (Monkeypatching) vs Mock Library
1. Menggunakan mock library menghasilkan code yang lebih clean.
2. Dalam mock library, cukup menggunakan decorator `patch` untuk menunjuk object yang ingin di-mock. Setelah test yang terdapat decorator tersebut dijalankan, object yang di-mock akan kembali ke bentuk awalnya sehingga tidak merubah object dalam test lain. Sementara dalam monkeypatching object tidak kembali ke keadaan awalnya sehingga dapat merusak test lain.
3. Mock library dapat me-record behaviour dari mock object yang dipanggil.
4. Mock library dapat me-return value spesifik dengan `return_value`, dan raise exception dengan `side_effect`.
\ No newline at end of file
import sys
from accounts.models import ListUser, Token
from accounts.models import User, Token
class PasswordlessAuthenticationBackend(object):
def authenticate(self, uid):
print('uid', uid, file=sys.stderr)
if not Token.objects.filter(uid=uid).exists():
print('no token found', file=sys.stderr)
return None
token = Token.objects.get(uid=uid)
print('got token', file=sys.stderr)
try:
user = ListUser.objects.get(email=token.email)
print('got user', file=sys.stderr)
return user
except ListUser.DoesNotExist:
print('new user', file=sys.stderr)
return ListUser.objects.create(email=token.email)
token = Token.objects.get(uid=uid)
return User.objects.get(email=token.email)
except User.DoesNotExist:
return User.objects.create(email=token.email)
except Token.DoesNotExist:
return None
def get_user(self, email):
return ListUser.objects.get(email=email)
\ No newline at end of file
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
\ No newline at end of file
# Generated by Django 2.1.7 on 2019-11-13 16:08
# -*- coding: utf-8 -*-
# Generated by Django 1.11.17 on 2019-11-19 09:42
from __future__ import unicode_literals
from django.db import migrations, models
......@@ -8,7 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
('auth', '0008_alter_user_username_max_length'),
]
operations = [
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.17 on 2019-11-20 15:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('email', models.EmailField(max_length=254, primary_key=True, serialize=False)),
],
),
]
......@@ -2,11 +2,12 @@ from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin
)
import uuid
class Token(models.Model):
email = models.EmailField()
uid = models.CharField(max_length=255)
uid = models.CharField(default=uuid.uuid4, max_length=40)
class ListUserManager(BaseUserManager):
......@@ -31,3 +32,10 @@ class ListUser(AbstractBaseUser, PermissionsMixin):
def is_active(self):
return True
class User(models.Model):
email = models.EmailField(primary_key=True)
REQUIRED_FIELDS = []
USERNAME_FIELD = 'email'
is_anonymous = False
is_authenticated = True
\ No newline at end of file
from django.test import TestCase
# Create your tests here.
from django.test import TestCase
from django.contrib.auth import get_user_model
from accounts.authentication import PasswordlessAuthenticationBackend
from accounts.models import Token
User = get_user_model()
class AuthenticateTest(TestCase):
def test_returns_None_if_no_such_token(self):
result = PasswordlessAuthenticationBackend().authenticate(
'no-such-token'
)
self.assertIsNone(result)
def test_returns_new_user_with_correct_email_if_token_exists(self):
email = 'edith@example.com'
token = Token.objects.create(email=email)
user = PasswordlessAuthenticationBackend().authenticate(token.uid)
new_user = User.objects.get(email=email)
self.assertEqual(user, new_user)
def test_returns_existing_user_with_correct_email_if_token_exists(self):
email = 'edith@example.com'
existing_user = User.objects.create(email=email)
token = Token.objects.create(email=email)
user = PasswordlessAuthenticationBackend().authenticate(token.uid)
self.assertEqual(user, existing_user)
class GetUserTest(TestCase):
def test_gets_user_by_email(self):
User.objects.create(email='another@example.com')
desired_user = User.objects.create(email='edith@example.com')
found_user = PasswordlessAuthenticationBackend().get_user(
'edith@example.com'
)
self.assertEqual(found_user, desired_user)
def test_returns_None_if_no_user_with_that_email(self):
self.assertIsNone(
PasswordlessAuthenticationBackend().get_user('edith@example.com')
)
\ No newline at end of file
from accounts.models import Token
from django.test import TestCase
from django.contrib.auth import get_user_model
User = get_user_model()
class UserModelTest(TestCase):
def test_user_is_valid_with_email_only(self):
user = User(email='a@b.com')
user.full_clean() # should not raise
class TokenModelTest(TestCase):
def test_links_user_with_auto_generated_uid(self):
token1 = Token.objects.create(email='a@b.com')
token2 = Token.objects.create(email='a@b.com')
self.assertNotEqual(token1.uid, token2.uid)
\ No newline at end of file
from django.test import TestCase
from unittest.mock import patch, call
from accounts.models import Token
class SendLoginEmailViewTest(TestCase):
def test_redirects_to_home_page(self):
response = self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
self.assertRedirects(response, '/')
@patch('accounts.views.send_mail')
def test_sends_mail_to_address_from_post(self, mock_send_mail):
self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
self.assertEqual(mock_send_mail.called, True)
(subject, body, from_email, to_list), kwargs = mock_send_mail.call_args
self.assertEqual(subject, 'Your login link for Superlists')
self.assertEqual(from_email, 'noreply@superlists')
self.assertEqual(to_list, ['edith@example.com'])
def test_adds_success_message(self):
response = self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
}, follow=True)
message = list(response.context['messages'])[0]
self.assertEqual(
message.message,
"Check your email, we've sent you a link you can use to log in."
)
self.assertEqual(message.tags, "success")
@patch('accounts.views.auth')
class LoginViewTest(TestCase):
def test_redirects_to_home_page(self, mock_auth):
response = self.client.get('/accounts/login?token=abcd123')
self.assertRedirects(response, '/')
def test_calls_authenticate_with_uid_from_get_request(self, mock_auth):
self.client.get('/accounts/login?token=abcd123')
self.assertEqual(
mock_auth.authenticate.call_args,
call(uid='abcd123')
)
def test_calls_auth_login_with_user_if_there_is_one(self, mock_auth):
response = self.client.get('/accounts/login?token=abcd123')
self.assertEqual(
mock_auth.login.call_args,
call(response.wsgi_request, mock_auth.authenticate.return_value)
)
def test_does_not_login_if_user_is_not_authenticated(self, mock_auth):
mock_auth.authenticate.return_value = None
self.client.get('/accounts/login?token=abcd123')
self.assertEqual(mock_auth.login.called, False)
\ No newline at end of file
......@@ -2,7 +2,7 @@ from django.conf.urls import url
from accounts import views
urlpatterns = [
url(r'^send_email$', views.send_login_email, name='send_login_email'),
url(r'^send_login_email$', views.send_login_email, name='send_login_email'),
url(r'^login$', views.login, name='login'),
url(r'^logout$', views.logout, name='logout'),
]
\ No newline at end of file
......@@ -2,32 +2,37 @@ import uuid
import sys
from django.shortcuts import render, redirect
from django.core.mail import send_mail
from django.contrib.auth import authenticate
from django.contrib import auth, messages
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.urls import reverse
from accounts.models import Token
def send_login_email(request):
email = request.POST['email']
uid = str(uuid.uuid4())
Token.objects.create(email=email, uid=uid)
print('saving uid', uid, 'for email', email, file=sys.stderr)
url = request.build_absolute_uri(f'/accounts/login?uid={uid}')
token = Token.objects.create(email=email)
url = request.build_absolute_uri(
reverse('login') + '?token=' + str(token.uid)
)
message_body = f'Use this link to log in:\n\n{url}'
send_mail(
'Your login link for Superlists',
f'Use this link to log in:\n\n{url}',
message_body,
'noreply@superlists',
[email],
)
return render(request, 'login_email_sent.html')
messages.success(
request,
"Check your email, we've sent you a link you can use to log in."
)
return redirect('/')
def login(request):
print('login view', file=sys.stderr)
uid = request.GET.get('uid')
user = authenticate(uid=uid)
if user is not None:
auth_login(request, user)
user = auth.authenticate(uid=request.GET.get('token'))
if user:
auth.login(request, user)
return redirect('/')
def logout(request):
......
from django.core import mail
from selenium.webdriver.common.keys import Keys
import re
from .base import FunctionalTest
TEST_EMAIL = 'edith@example.com'
SUBJECT = 'Your login link for Superlists'
class LoginTest(FunctionalTest):
def test_can_get_email_link_to_log_in(self):
# Edith goes to the awesome superlists site
# and notices a "Log in" section in the navbar for the first time
# It's telling her to enter her email address, so she does
self.browser.get(self.live_server_url)
self.browser.find_element_by_name('email').send_keys(TEST_EMAIL)
self.browser.find_element_by_name('email').send_keys(Keys.ENTER)
# A message appears telling her an email has been sent
self.wait_for(lambda: self.assertIn(
'Check your email',
self.browser.find_element_by_tag_name('body').text
))
# She checks her email and finds a message
email = mail.outbox[0]
self.assertIn(TEST_EMAIL, email.to)
self.assertEqual(email.subject, SUBJECT)
# It has a url link in it
self.assertIn('Use this link to log in', email.body)
url_search = re.search(r'http://.+/.+$', email.body)
if not url_search:
self.fail(f'Could not find url in email body:\n{email.body}')
url = url_search.group(0)
self.assertIn(self.live_server_url, url)
# she clicks it
self.browser.get(url)
# she is logged in!
# self.wait_for(
# lambda: self.browser.find_element_by_link_text('Log out')
# )
# navbar = self.browser.find_element_by_css_selector('.navbar')
# self.assertIn(TEST_EMAIL, navbar.text)
\ No newline at end of file
......@@ -17,8 +17,8 @@ class NewVisitorTest(FunctionalTest):
self.browser.get(self.live_server_url)
# She notices the page title and header mention to-do lists
self.assertIn('To-Do', self.browser.title)
header_text = self.browser.find_element_by_tag_name('h1').text
self.assertIn('To-Do', header_text)
header_text = self.browser.find_element_by_tag_name('h2').text
self.assertIn('Rahmania Astrid Mochtar', header_text)
# She is invited to enter a to-do item straight away
inputbox = self.browser.find_element_by_id('id_new_item')
......
......@@ -21,13 +21,29 @@
<p>Logged in as {{ user.email }}</p>
<p><a id="id_logout" href="{% url 'logout' %}">Log out</a></p>
{% else %}
<form method="POST" action ="{% url 'send_login_email' %}">
<form class="navbar-form navbar-right"
method="POST"
action="{% url 'send_login_email' %}">
Enter email to log in: <input name="email" type="text" />
{% csrf_token %}
</form>
{% endif %}
</div>
{% if messages %}
<div class="row">
<div class="col-md-8">
​{% for message in messages %}
​{% if message.level_tag == 'success' %}
<div class="alert alert-success">{{ message }}</div>
​{% else %}
<div class="alert alert-warning">{{ message }}</div>
​{% endif %}
​{% endfor %}
</div>
</div>
​{% endif %}
<div class="row">
<div class="col-md-6 col-md-offset-3 jumbotron">
<div class="text-center">
......
......@@ -46,7 +46,7 @@ INSTALLED_APPS = [
'accounts',
]
AUTH_USER_MODEL = 'accounts.ListUser'
AUTH_USER_MODEL = 'accounts.User'
AUTHENTICATION_BACKENDS = [
'accounts.authentication.PasswordlessAuthenticationBackend',
]
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment