Fakultas Ilmu Komputer UI

Commit aceba63b authored by Muhammad Ilham Peruzzi's avatar Muhammad Ilham Peruzzi
Browse files

Selesai exercise8 - bagian 1

parent 04efa7ae
Pipeline #25350 passed with stages
in 9 minutes and 21 seconds
......@@ -138,4 +138,14 @@ Terlihat score yang didapatkan meningkat menjadi 61.9% dan mutant yang survived
# Cerita Exercise 7
Pada Exercise 7 ini diajarkan sebuah konsep yang dinamakan spiking dan de-spiking. Konsep spiking dan de-spiking ini adalah metode yang digunakan saat kita ingin mencoba mengimplememtasikan sebuah fitur tanpa membuat testnya terlebih dahulu. Spiking adalah kegiatan untuk melakukan implementasi tanpa membuat test terlebih dahulu untuk melihat bagaimana program bekerja dan merasakan 'feel' mengimplementasikan fitur tersebut. Pada exercise 7 ini, spiking dilakukan saat kita mencoba membuat fitur autentikasi, dikarenakan fitur autentikasi sedikit kompleks sehingga kita harus tau bagaimana mengimplementasikannya sebelum membuat test. De-spiking adalah melakukan ulang implementasi yang dilakukan saat spiking dengan menggunakan konsep TTD dan kaidah pemprogramman yang benar. Saat Exercise 7 konsep de-spiking diterapkan saat kita merevert kode dan menghapus folder acccounts untuk dibuat ulang dengan mengikuti kaidah TTD.
\ No newline at end of file
Pada Exercise 7 ini diajarkan sebuah konsep yang dinamakan spiking dan de-spiking. Konsep spiking dan de-spiking ini adalah metode yang digunakan saat kita ingin mencoba mengimplememtasikan sebuah fitur tanpa membuat testnya terlebih dahulu. Spiking adalah kegiatan untuk melakukan implementasi tanpa membuat test terlebih dahulu untuk melihat bagaimana program bekerja dan merasakan 'feel' mengimplementasikan fitur tersebut. Pada exercise 7 ini, spiking dilakukan saat kita mencoba membuat fitur autentikasi, dikarenakan fitur autentikasi sedikit kompleks sehingga kita harus tau bagaimana mengimplementasikannya sebelum membuat test. De-spiking adalah melakukan ulang implementasi yang dilakukan saat spiking dengan menggunakan konsep TTD dan kaidah pemprogramman yang benar. Saat Exercise 7 konsep de-spiking diterapkan saat kita merevert kode dan menghapus folder acccounts untuk dibuat ulang dengan mengikuti kaidah TTD.
# Cerita Exercise 8
## Perbedaan Antara Manual Mocking dan Mocking Menggunakan Library
Pada saat membuat suatu test case, terkadang kita ingin melakukan test terhadap fungsi yang memiliki beberapa dampak eksternal, seperti penggunaan API, mengirim email, dan lain-lain. Tentu kita tidak ingin saat test dijalankan, test tersebut mengirimkan data asli ke luar program melalui internet publik. Untuk itu, diperlukan suatu teknik agar kita dapat melakukan test terhadap fungsi tersebut namun tidak memiliki eksternal efek. Teknik tersebut disebut mocking. Terdapat dua jenis cara melakukan mocking, yaitu secara manual atau menggunakan mocking library.
Saat melakukan mocking secara manual, kita menggantikan fungsi yang akan ditest dengan mock yang kita buat sendiri. Teknik ini dinamakan monkeypatching, dimana kita memodifikasi fungsi agar sesuai dengan alur program dengan data-data yang bersifat local (hanya berjalan saat test). Melakukan mocking manual harus dilakukan sebelum fungsi yang ingin di test dipanggil, agar fungsi original dapat diganti terlebih dahulu dengan mock yang dibuat.
Berbeda dengan mocking manual, saat melakukan mocking dengan library kita tidak perlu melakukan setup mock sendiri, dikarenakan sudah ada library yang menyediakan dan mempersiapkan mock tersebut, sehingga kita hanya mengatur return value apa dan bagaimana mock tersebut berjalan. Salah satu library mock adalah unittest.mock dan unittest.patch yang terdapat pada Python 3.3.
\ No newline at end of file
import sys
from accounts.models import ListUser, Token
from accounts.models import Token, User
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.2.5 on 2019-11-18 11:17
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_token'),
]
operations = [
migrations.AlterField(
model_name='token',
name='uid',
field=models.CharField(default=uuid.uuid4, max_length=40),
),
]
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
import accounts.views
from unittest.mock import patch, call
from accounts.models import Token
@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_sends_mail_to_address_from_post_manual(self, mock_auth):
self.send_mail_called = False
def fake_send_mail(subject, body, from_email, to_list):
self.send_mail_called = True
self.subject = subject
self.body = body
self.from_email = from_email
self.to_list = to_list
accounts.views.send_mail = fake_send_mail
self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
self.assertTrue(self.send_mail_called)
self.assertEqual(self.subject, 'Your login link for Superlists')
self.assertEqual(self.from_email, 'noreply@superlists')
self.assertEqual(self.to_list, ['edith@example.com'])
@patch('accounts.views.send_mail')
def test_sends_mail_to_address_from_post(self, mock_send_mail, mock_auth):
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, mock_auth):
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")
def test_creates_token_associated_with_email(self, mock_auth):
self.client.post('/accounts/send_login_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, mock_auth):
self.client.post('/accounts/send_login_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)
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,5 +2,6 @@ from django.conf.urls import url
from accounts import views
urlpatterns = [
url(r'^send_login_email$', views.send_login_email, name='send_login_email'),
url(r'^login$', views.login, name='login'),
]
\ No newline at end of file
......@@ -4,5 +4,31 @@ from django.contrib.auth import authenticate
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.core.mail import send_mail
from django.shortcuts import redirect, render
from django.contrib import auth, messages
from accounts.models import Token
from django.urls import reverse
def send_login_email(request):
email = request.POST['email']
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',
message_body,
'noreply@superlists',
[email]
)
messages.success(
request,
"Check your email, we've sent you a link you can use to log in."
)
return redirect('/')
def login(request):
user = auth.authenticate(uid=request.GET.get('token'))
if user:
auth.login(request, user)
return redirect('/')
\ No newline at end of file
......@@ -17,13 +17,27 @@
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<a class="navbar-brand" href="/">Superlists</a>
<form class="navbar-form navbar-right" method="POST" action="#">
<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>
</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 col-md-offset-3 jumbotron card-body">
......
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