[Bob Morgan] 소셜 로그인 Unit Test

이태권 (Taekwon Lee)·2022년 7월 8일
0

[Project] Bob Morgan

목록 보기
3/6

[밥 먹언] 소셜 로그인 유닛 테스트

🎉 Unit Test 결과 (성공)

난생 처음 해 보는 unit test라 너무 힘들었지만..
결실을 맺었다😭
디버깅 하는데 몇 시간을 소비한 것인가...



bob_morgan/urls.py

from django.urls import path, include

urlpatterns = [
    path('users', include('users.urls')),
]


users/urls.py

from django.urls import path

from users.views import KakaoSigninView
urlpatterns = [
    path('/signin/kakao', KakaoSigninView.as_view())
]


모델링

  • 아래와 같이 userskakao_id가 있지 않고 두 개의 테이블이 더 생성되어 외부 키로 연결되어 있다.
  • platformsserial_number가 있으며 이것이 해당 플랫폼의 고유 id를 뜻한다.
  • social_networkskakaonaver와 같이 소셜 로그인 기능을 지원하는 소셜 네트워크가 있다.



users/models.py

  • passwordnull=True인 이유는 소셜 로그인을 하였을 때 비밀번호가 저장되지 않기 때문이다.
  • profile_image_url에 default 값을 넣어 둔 것은 사용자가 프로필 이미지를 선택 사항으로 동의하지 않았을 때를 위해 넣어두었다.
from django.db import models

from places.models import Place
from core.models   import TimeStampModel

class User(TimeStampModel):
    email             = models.CharField(max_length=80, null=True, unique=True)
    password          = models.CharField(max_length=200, null=True)
    nickname          = models.CharField(max_length=80, null=True)
    profile_image_url = models.CharField(max_length=1000, default='https://cdn-icons-png.flaticon.com/512/1450/1450252.png')
    date_of_birth     = models.DateField(null=True)
    platform          = models.ForeignKey('Platform', on_delete=models.CASCADE, null=True)

    class Meta:
        db_table = 'users'

class WishList(TimeStampModel):
    place = models.ForeignKey(Place, on_delete=models.CASCADE)
    user  = models.ForeignKey('User', on_delete=models.CASCADE)

    class Meta:
        db_table = 'wish_lists'

class Platform(models.Model):
    serial_number  = models.BigIntegerField()
    social_network = models.ForeignKey('SocialNetwork', on_delete=models.CASCADE)

    class Meta:
        db_table = 'platforms'

class SocialNetwork(models.Model):
    name = models.CharField(max_length=100, unique=True)

    class Meta:
        db_table = 'social_networks'


users/views.py

🎙 설명

  • get_or_create를 통해 없을 경우 create 있을 경우 get을 이용한다.

수정 사항

  • User.objects.get_or_create 조건에 dafaults만 넣어두니 아래와 같은 에러가 발생하였다.
======================================================================
FAIL: test_success_kakao_signup (users.tests.KakaoSigninTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/taekwon/miniconda3/envs/bob_morgan/lib/python3.9/unittest/mock.py", line 1336, in patched
    return func(*newargs, **newkeywargs)
  File "/Users/taekwon/Desktop/bob_morgan/users/tests.py", line 63, in test_success_kakao_signup
    self.assertEqual(response.status_code, 201)
AssertionError: 200 != 201

----------------------------------------------------------------------

status code 201이 떠야 하는데 get만 하니200이 뜨는 것이다.

  • access_token에서 인코딩 하는 아이디를 platform에서 user.id로 바꾸었다.

🖥 수정 이전 Code

import jwt, requests

from django.http  import JsonResponse
from django.views import View
from django.conf  import settings

from users.models import User, SocialNetwork, Platform

class KakaoSigninView(View):
    def get(self, request):
        try:
            kakao_token = request.headers.get('Authorization')
            KAKAO_URL   = 'https://kapi.kakao.com/v2/user/me'
            headers     = {'Authorization': f'Bearer {kakao_token}'}
            response    = requests.get(KAKAO_URL, headers=headers, timeout=3)
            kakao_user  = response.json()

            kakao_id          = kakao_user['id']
            kakao_nickname    = kakao_user['kakao_account']['profile']['nickname']
            kakao_email       = kakao_user['kakao_account']['email']
            profile_image_url = kakao_user['kakao_account']['profile']['profile_image_url']

            kakao                 = SocialNetwork.objects.get(name='kakao').id
            platform, is_created  = Platform.objects.get_or_create(
                serial_number     = kakao_id,
                social_network_id = kakao
            )

            platform, is_created  = User.objects.get_or_create(
                defaults = {
                    'platform_id'       : platform.id,
                    'nickname'          : kakao_nickname,
                    'email'             : kakao_email,
                    'profile_image_url' : profile_image_url,
                }
            )

            access_token = jwt.encode({'id': platform.id}, settings.SECRET_KEY, settings.ALGORITHM)

            if is_created:
                return JsonResponse({'message' : 'ACCOUNT_CREATED', 'access_token': access_token}, status=201)
            else:
                return JsonResponse({'message' : 'SIGN_IN_SUCCESS', 'access_token': access_token}, status=200)

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)

🖥 수정 이후 Code

import jwt, requests

from django.http  import JsonResponse
from django.views import View
from django.conf  import settings

from users.models import User, SocialNetwork, Platform

class KakaoSigninView(View):
    def get(self, request):
        try:
            kakao_token = request.headers.get('Authorization')
            KAKAO_URL   = 'https://kapi.kakao.com/v2/user/me'
            headers     = {'Authorization': f'Bearer {kakao_token}'}
            response    = requests.get(KAKAO_URL, headers=headers, timeout=3) 
            kakao_user  = response.json()

            kakao_id          = kakao_user['id']
            kakao_nickname    = kakao_user['kakao_account']['profile']['nickname']
            kakao_email       = kakao_user['kakao_account']['email']
            profile_image_url = kakao_user['kakao_account']['profile']['profile_image_url']

            kakao                 = SocialNetwork.objects.get(name='kakao').id
            platform, is_created  = Platform.objects.get_or_create(
                serial_number     = kakao_id,
                social_network_id = kakao
            )

            user, is_created  = User.objects.get_or_create(
                platform = platform,
                defaults = {
                    'platform'          : platform,
                    'nickname'          : kakao_nickname,
                    'email'             : kakao_email,
                    'profile_image_url' : profile_image_url,
                }
            )

            access_token = jwt.encode({'id': user.id}, settings.SECRET_KEY, settings.ALGORITHM)

            if is_created:
                return JsonResponse({'message' : 'ACCOUNT_CREATED', 'access_token': access_token}, status=201)
            else:
                return JsonResponse({'message' : 'SIGN_IN_SUCCESS', 'access_token': access_token}, status=200)

        except KeyError:
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)


users/tests.py

import jwt

from django.test   import Client, TestCase
from django.conf   import settings
from unittest.mock import patch, MagicMock

from users.models import Platform, User, SocialNetwork
class KakaoSigninTest(TestCase):
    def setUp(self):
        SocialNetwork.objects.create(
            id   = 1,
            name = 'kakao',
        )
        Platform.objects.create(
            id                = 123456,
            serial_number     = 987654,
            social_network_id = SocialNetwork.objects.get(name='kakao').id,
        )
        User.objects.create(
            id          = 5,
            email       = 'test@kakao.com',
            nickname    = 'test',
            platform_id = Platform.objects.get(id=123456).id,
        )

    def tearDown(self):
        SocialNetwork.objects.all().delete()
        Platform.objects.all().delete()
        User.objects.all().delete()

    @patch('users.views.requests')
    def test_success_kakao_signup(self, mocked_requests):
        client = Client()
        class MockedResponse:
            def json(self):
                return {
                    'id': 11111111,
                    'connected_at' : '2022-07-07T04:35:56Z',
                    'properties'   : {
                        'nickname'       : 'signup',
                        'profile_image'  : 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_640x640.jpg',
                        'thumbnail_image': 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_110x110.jpg'},
                    'kakao_account': {
                        'profile_nickname_needs_agreement': False,
                        'profile_image_needs_agreement': False,
                        'profile': {
                            'nickname': 'signup',
                            'thumbnail_image_url': 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_110x110.jpg',
                            'profile_image_url': 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_640x640.jpg',
                            'is_default_image': False
                        },
                        'has_email': True,
                        'email_needs_agreement': False,
                        'is_email_valid': True,
                        'is_email_verified': True,
                        'email': 'kakao-signup@kakao.com'
                    }
                }



        mocked_requests.get = MagicMock(return_value = MockedResponse())
        headers             = {'HTTP_Authorization': 'mocked_access_token'}
        response            = client.get('/users/signin/kakao', **headers)
        access_token        = jwt.encode({'id': User.objects.latest('id').id}, settings.SECRET_KEY, settings.ALGORITHM)
        self.assertEqual(response.status_code, 201)
        self.assertEqual(
            response.json(),{
                'message'      : 'ACCOUNT_CREATED',
                'access_token' : access_token
            }
        )

    @patch('users.views.requests')
    def test_success_kakao_signin(self, mocked_requests):
        client = Client()

        class MockedResponse:
            def json(self):
                return {
                    'id': 987654,
                    'connected_at' : '2022-07-07T04:35:56Z',
                    'properties'   : {
                        'nickname'       : 'test',
                        'profile_image'  : 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_640x640.jpg',
                        'thumbnail_image': 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_110x110.jpg'},
                    'kakao_account': {
                        'profile_nickname_needs_agreement': False,
                        'profile_image_needs_agreement': False,
                        'profile': {
                            'nickname': 'test',
                            'thumbnail_image_url': 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_110x110.jpg',
                            'profile_image_url': 'http://k.kakaocdn.net/dn/bxmEfD/btryJYaBk0R/oPpMxh2NK73LSYT98Xl1Vk/img_640x640.jpg',
                            'is_default_image': False
                        },
                        'has_email': True,
                        'email_needs_agreement': False,
                        'is_email_valid': True,
                        'is_email_verified': True,
                        'email': 'test@kakao.com'
                    }
                }
        mocked_requests.get = MagicMock(return_value = MockedResponse())
        headers             = {'HTTP_Authorization': 'mocked_access_token'}
        response            = client.get('/users/signin/kakao', **headers)
        access_token        = jwt.encode({'id': User.objects.latest('id').id}, settings.SECRET_KEY, settings.ALGORITHM)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json(),{
            'message'      : 'SIGN_IN_SUCCESS',
            'access_token' : access_token
            }
        )
profile
(Backend Dev.) One step at a time

0개의 댓글