소셜로그인이 끝나고 한참이 지나고서야 칭찬(?)받은,,, 박제하고 싶은 내 메모들,,, 2차도 역시 스파르타 학습가이드,,, 너무 좋다구여,,,
카카오 소셜 로그인 API를 활용해서, 기능을 구현하는데 있어서 가장 고민이 깊었던 부분은
1. 프론트와 백이 통신하는 시점
2. 백과 카카오가 통신하는 시점
3. 백에서 카카오 서버에 어떻게 request를 보내지?
4. 그리고 return과 raise와 unittests
이들과 나의 싸움이 시작됐다 흫
requests document + Kakao request url
핵심은,
back은 request에 대하여 응답만 했지 보낼수도 있었고!
처음 kakao document를 읽었을 때, 이 질문에 대하여 끊임없는 답답함의 연속, 그것이 엄청났던 것이 떠오른다.
(그래서 requests 쓰면 되는거냐는 질문에 그 누구도 대답을 해주지 않았음 흫)
MH님과 리퀘스트도큐먼트대탐험 과 카카오도큐먼트대탐험하면서,
고통스러웠지만,
이곳에 도착했을 때,
진짜 세상 너무 재밌고 너무 좋았던!
그 방에서,,, 우릴 보는 멘토님들,,, 우리 얼마나 욱겨보였을까,,, 그렇게 좋아했는데,,,
그렇게 리퀘스트유알엘 만들기 대장정이 끝나면 이제 뷰만 쓰면 되지?
되,,,,,,지,,,,,???
일단 의식의 흐름대로
쓴다. 한글로. 뷰를
class SignInView(View):
def get(self, request):
try:
#1. front로 부터 토큰 받기 > headers
#2. 카카오로 user 정보 요청하기
#3. 정보받기(kakao id, email, 이름)
# 받아올 정보 kakao_account 추가 ()
#4. user id 가져오기 kakao_id = result["id"]
#5. user table에 확인 > get or create / > user table에 저장
#6. 있는 user라면 jwt token 발급
#7. front 전달
#8. error 처리
access_token = request.headers.get('Authorization', None)
# print(access_token)
headers = {"Authorization" : f"Bearer {access_token}"}
url = "https://kapi.kakao.com/v2/user/me?secure_resource=true&property_keys=%5B%22properties.nickname%22%2C%22kakao_account.email%22%5D"
result = requests.get(url, headers = headers)
# print(result)
kakao_data = result.json()
if result.status_code != 200:
return JsonResponse({'message' : result.json()}, status = result.status_code)
# result.json()
kakao_data = result.json()
kakao_id = kakao_data['id']
# print(kakao_id)
email = kakao_data['kakao_account']["email"]
# print(email)
name = kakao_data['properties']["nickname"]
# print(name)
user, created = User.objects.get_or_create(
kakao_id = kakao_id,
defaults = {
'name' : name,
'email' : email,
}
)
token = jwt.encode({'id': user.id}, SECRET_KEY, ALGORITHM)
return JsonResponse({'message' : 'SUCCESS', 'token' : token}, status = 201)
except KeyError:
return JsonResponse({'message' : 'UNAUTHORIZED'}, status = 401)
class LogOutView(View):
def post(self, request):
access_token = request.headers.get('Authorization', None)
headers = {"Authorization" : f"Bearer {access_token}"}
url = "https://kapi.kakao.com/v1/user/logout"
result = requests.post(url, headers = headers)
if result.status_code != 200:
return JsonResponse({'message' : result.json()}, status = result.status_code)
return JsonResponse({'kakao_id' : result.json()}, status = 200)
리뷰 & 리뷰 & 리뷰
B. Kakao에서 request로 받은 json 메세지 활용하여 1차 에러처리
에러 처리 : KakaoAPI 함수에서 에러코드를 1차로 처리 및 카카오에서 보낸 에러 메세지와 status code를 그대로 사용하도록 구현 > status_code가 200이 아닌 경우, 함수를 끝내고 이후 과정을 진행하지 않도록 하는 로직을 구현하고자 함
1) return : 에러가 발생하더라도, 그대로 에러 메세지를 담아 KakaoLoginView를 실행하면서 결과적으로 error 발생
또한 이 경우, view > (module) > db > View로 다시 돌아와 종료 되는 것이 아닌 모듈에서 json response를 반환해버리고 종료 되는 것은 정상 루트가 아니라고 할 수 있는데 그 개념이 뭐였더라,,,,,;;;;
2) raise : 해당 에러를 반환해주는 error를 exception으로 발생시키고 KakaoLoginView에서 에러 메세지를 출력(e)해줄 수 있지만, 어떤 에러인지 정확하게 파악할 수 없음
또한 이 경우, unittest를 진행하였을 때 아래와 같은 에러 발생
TypeError: catching classes that do not inherit from BaseException is not allowed
결과 : KakaoAPI에서의 에러 처리를 하지 않고, 모든 response를 KakaoLoginView로 그대로 반환하고, 1차 에러 처리함(return)
C. unittests
AttributeError: 'MockedResponse' object has no attribute 'status_code'
if not response.status_code == -401:
AttributeError: 'MockedResponse' object has no attribute 'status_code'
During handling of the above exception, another exception occurred
.__dir__()
를 찍어보면???['__module__', 'json', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
if kakao_response.get('code') == -401:
과 같이class SignInTest(TestCase):
@patch("core.utils.KakaoAPI.requests")
def test_kakao_signin_new_user_success(self, mocked_requests):
client = Client()
class MockedResponse:
status_code = 200
def json(self):
return {
"id": 2033314461,
"connected_at": "2021-12-14T09:03:16Z",
"properties": {
"nickname": "김은혜"
},
"kakao_account": {
"has_email": True,
"email_needs_agreement": False,
"is_email_valid": True,
"is_email_verified": True,
"email": "jino63@naver.com"
}
}
mocked_requests.get = mock.MagicMock(return_value = MockedResponse())
headers = {"HTTP_Authorization" : "access_token"}
response = client.post("/users/signin", **headers)
self.assertEqual(response.status_code, 201)
status_code = 200
라고 선언을 해주고,
print(MockedResponse().__dir__())
를 찍어보면, 놀랍게도
'__module__', 'status_code', 'json', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__'
해결? 아직,
D. object(객체)와 dictionary / status_code
결론
return으로 jsonresponse를 반환하는 것은, validator때도 이미 여러차례, 데코레이터 선생님을 붙잡고 공부를 했지만, try 안의 함수(class) 호출 부분 이후 부터의 코드를 그대로 실행하기 때문에, 의도한 로직에 의하면 맞지 않다.
결국 raise로 error를 발생시키고 이후 함수는 종료 시켜야 하는데, unittests를 통과하지 않으며(typeerror???), 메세지(e)를 준다고 해도 무슨 에러인지 정확하게 반환하지 않음.
그럼 어짜피 view로 돌아와 종료해야 함과 의도했던 로직(카카오에서 돌아온 response가 200이 아니면 종료)을 실행하기 위해서 모듈에서 response를 (200이든 -400이든 간에) view로 전달하고, 판단은 view에서 하는 것,,,
여기까지가 맞다고 인정이 되고,
결국 이 삼위일체 찐막으로 리팩토링 하지 못한 것이 끝끝내 신경쓰이는데 조만간
한다. 래이즈. 한다. 유닛테스트.
하 이게 이렇게 된 데는 총 3명의 멘토님의 리뷰가 있었음인데 너무 썰 풀고 싶네 :-)
이프낫이백이 불러온 파급효과,,,,,