Fakultas Ilmu Komputer UI

Commit daea54f4 authored by Syahrul Findi's avatar Syahrul Findi
Browse files

Latihan8

parent 464bc4c0
......@@ -23,3 +23,9 @@ Test yang saya buat sebelumnya sudah strongly killed, sehingga saya tidak perlu
KEtika kita mencoba teknologi atau tools baru, terkadang kita ingin tahu bagaimana cara kerjanya tanpa perlu terganggung oleh pembuatan unit test terlebih dahulu. Pun jika kita belum tahu dan langsung membuat unit test untuk teknologi tersebut, hasilnya belum tentu benar. Oleh sebab itu, dalam TDD diperbolehkan untuk membuat prototype dari fitur yang akan ditest tanpa membuat test-nya terlebih dahulu, yang disebut dengan "Spiking".
Ketika kita sudah mengetahui bagaimana implementasi dari teknologi yang kita pelajari, barulah kita bisa membuat functional test dari fitur tersebut. Setelah test dibuat, kita dapat menulis kembali prototype yang telah kita buat tadi dan kembali menerapkan TDD seperti biasanya. Proses tersebut dinamakan "De-Spiking".
## Latihan 8
Manual mocking adalah implementasi mock dengan cara membuat sendiri method mock dari sebuah method yang mengembalikan nilai yang kita inginkan. Sedangkan jika kita menggunakan mock library, mock method dari method yang kita inginkan akan dibuatkan oleh library tersebut, kita hanya tinggal mengatur behavior dari mock tersebut.
Mocking dilakukan pada sebuah method yang dipanggil saat sebuah method dijalankan pada sebuah test. Artinya dalam implementasi method tersebut, setiap pemanggilannya akan bergantung pada method yang dibuat mocknya. Sebuah class/method yang memiliki ketergantungan tinggi dengan class/method lainnya disebut tightly coupled. Misalnya pada source code, setiap pemanggilan method GET untuk url login akan memanggil method auth pada accounts.views. Oleh sebab itu dapat disimpulkan bahwa method login tightly coupled dengan method auth.
import sys
from accounts.models import ListUser, Token
from accounts.models import Token, User
class PasswordlessAuthenticationBackend(object):
def authenticate(self, uid):
if not Token.objects.filter(uid=uid).exists():
return None
token = Token.objects.get(uid=uid)
try:
user = ListUser.objects.get(email=token.email)
return user
except ListUser.DoesNotExist:
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)
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
# -*- coding: utf-8 -*-
# Generated by Django 1.11.24 on 2019-11-21 16:31
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_auto_20191112_1457'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('email', models.EmailField(max_length=254, primary_key=True, serialize=False)),
],
),
]
import uuid
from django.db import models
from django.contrib import auth
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin
)
auth.signals.user_logged_in.disconnect(auth.models.update_last_login)
class User(models.Model):
email = models.EmailField(primary_key=True)
REQUIRED_FIELDS = []
USERNAME_FIELD = 'email'
is_anonymous = False
is_authenticated = True
class ListUserManager(BaseUserManager):
def create_user(self, email):
......
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 django.test import TestCase
from django.contrib.auth import get_user_model
from django.contrib import auth
from accounts.models import Token
User = get_user_model()
User = auth.get_user_model()
class UserModelTest(TestCase):
......@@ -14,6 +14,12 @@ class UserModelTest(TestCase):
user = User(email='a@b.com')
self.assertEqual(user.pk, 'a@b.com')
def test_no_problem_with_auth_login(self):
user = User.objects.create(email='edith@example.com')
user.backend = ''
request = self.client.request().wsgi_request
auth.login(request, user)
class TokenModelTest(TestCase):
def test_links_user_with_auto_generated_uid(self):
......
from django.test import TestCase
from unittest.mock import patch, call
from accounts.models import Token
import accounts.views
class SendLoginEmailViewTest(TestCase):
def test_redirects_to_home_page(self):
response = self.client.post('/accounts/send_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_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_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")
def test_creates_token_associated_with_email(self):
self.client.post('/accounts/send_email', data={
'email': 'edith@example.com'
})
token = Token.objects.first()
self.assertEqual(token.email, 'edith@example.com')
@patch('accounts.views.send_mail')
def test_sends_link_to_login_using_token_uid(self, mock_send_mail):
self.client.post('/accounts/send_email', data={
'email': 'edith@example.com'
})
token = Token.objects.first()
expected_url = f'http://testserver/accounts/login?token={token.uid}'
(subject, body, from_email, to_list), kwargs = mock_send_mail.call_args
self.assertIn(expected_url, body)
@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)
from django.conf.urls import url
from accounts import views
from django.contrib.auth.views import logout
urlpatterns = [
url(r'^send_email$', views.send_login_email, name='send_login_email'),
url(r'^login$', views.login, name='login'),
url(r'^logout$', views.logout, name='logout'),
url(r'^logout$', logout, {'next_page': '/'}, name='logout'),
]
import uuid
import sys
from django.core.urlresolvers import reverse
from django.contrib import auth, messages
from django.contrib.auth import authenticate
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.shortcuts import redirect, render
......@@ -9,23 +11,28 @@ 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)
url = request.build_absolute_uri('/accounts/login?uid=%s' % uid)
token = Token.objects.create(email=email)
url = request.build_absolute_uri(
reverse('login') + '?token=' + str(token.uid)
)
message_body = 'Use this link to log in:\n\n%s' % url
send_mail(
'Your login link for Superlists',
'Use this link to log in:\n\n%s' % 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):
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('/')
......
......@@ -31,7 +31,7 @@ class LoginTest(FunctionalTest):
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}')
self.fail('Could not find url in email body:\n%s' % email.body)
url = url_search.group(0)
self.assertIn(self.live_server_url, url)
......@@ -44,3 +44,19 @@ class LoginTest(FunctionalTest):
)
navbar = self.browser.find_element_by_css_selector('.navbar')
self.assertIn(TEST_EMAIL, navbar.text)
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)
# Now she logs out
self.browser.find_element_by_link_text('Log out').click()
# She is logged out
self.wait_for(
lambda: self.browser.find_element_by_name('email')
)
navbar = self.browser.find_element_by_css_selector('.navbar')
self.assertNotIn(TEST_EMAIL, navbar.text)
......@@ -46,7 +46,7 @@ INSTALLED_APPS = [
'accounts',
]
AUTH_USER_MODEL = 'accounts.ListUser'
AUTH_USER_MODEL = 'accounts.User'
AUTHENTICATION_BACKENDS = [
'accounts.authentication.PasswordlessAuthenticationBackend',
]
......
......@@ -10,17 +10,39 @@
</head>
<body>
<div class="container">
<div class="navbar">
{% if user.is_authenticated %}
<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' %}">
Enter email to log in: <input name="email" type="text" />
{% csrf_token %}
</form>
{% endif %}
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<a class="navbar-brand" href="/">Superlists</a>
{% if user.email %}
<ul class="nav navbar-nav navbar-right">
<li class="navbar-text">Logged in as {{ user.email }}</li>
<li><a href="{% url 'logout' %}">Log out</a></li>
</ul>
{% else %}
<form
class="navbar-form navbar-right"
method="POST"
action="{% url 'send_login_email' %}"
>
<span>Enter email to log in:</span>
<input class="form-control" name="email" type="text" />
{% csrf_token %}
</form>
{% endif %}
</div>
</nav>
{% 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 offset-md-3 jumbotron">
......
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