diff --git a/.travis.yml b/.travis.yml index 8f39ef3..cfaff7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ install: - pip install nose - pip install mock - pip install coveralls + - pip install flask script: nosetests --with-coverage --cover-package=kakaoplus after_success: coveralls diff --git a/example.py b/example.py index 87e8baa..cd20ed1 100644 --- a/example.py +++ b/example.py @@ -1,44 +1,23 @@ #-*- coding:utf-8 -*- -from flask import Flask, request - +from flask import Flask from kakaoplus import KaKaoAgent - app = Flask(__name__) -KaKao = KaKaoAgent() - -@app.route('/', methods=['GET','POST']) -def app_start(): - return "App launched Success" - - -@app.route('/keyboard', methods=['GET']) -def keyboard_handler(): - return KaKao.handle_keyboard_webhook() - +KaKao = KaKaoAgent(app, '/') -@app.route('/message', methods=['POST']) -def message_handler(): - req = request.get_data(as_text=True) - - return KaKao.handle_webhook(req) @KaKao.handle_keyboard def handle_keywboard(res): res.text = True - @KaKao.handle_message def handle_message(req, res): echo_message = req.content - res.text = "Echo !!" + echo_message - @KaKao.handle_message(['hello', 'hi']) def greeting_callback(req, res): res.text = 'hello my friend' - if __name__ == "__main__": app.run() \ No newline at end of file diff --git a/kakaoplus/kakaoplus.py b/kakaoplus/kakaoplus.py index 0f5bfe5..d9c3b85 100644 --- a/kakaoplus/kakaoplus.py +++ b/kakaoplus/kakaoplus.py @@ -1,15 +1,13 @@ import json import re +from flask import request as flask_request from .payload import Payload, KeyboardPayload from .utils import PY3, _byteify, LOGGER class Req(object): def __init__(self, data): - if not PY3: - self.data = json.loads(data, object_hook=_byteify) - else: - self.data = json.loads(data) + self.data = data @property def user_key(self): @@ -39,7 +37,46 @@ class KaKaoAgent(object): _photo_handler = None _default_callback = None - def handle_webhook(self, request): + def __init__(self, app=None, route='/', blueprint=None): + self.app = app + if route[-1] != '/': + route += '/' + self._route = route + + if app is not None: + self.init_app(app) + elif blueprint is not None: + self.init_blueprint(blueprint) + else: + pass + + + def init_app(self, app): + app.kakao = self + + app.add_url_rule(self._route + "keyboard", view_func=self._handle_keyboard_webhook, methods=['GET']) + app.add_url_rule(self._route + "message", view_func=self._handle_webhook, methods=['POST']) + + + def init_blueprint(self, blueprint): + blueprint.kakao = self + + blueprint.add_url_rule("/keyboard", view_func=self._handle_keyboard_webhook, methods=['GET']) + blueprint.add_url_rule("/message", view_func=self._handle_webhook, methods=['POST']) + + + def _kakaoplus_req(self): + raw_body = flask_request.data + if not PY3: + kakaoplus_request_payload = json.loads(raw_body, object_hook=_byteify) + else: + kakaoplus_request_payload = json.loads(raw_body) + + return kakaoplus_request_payload + + + def _handle_webhook(self): + request = self._kakaoplus_req() req = Req(request) res = Payload() @@ -63,7 +100,8 @@ def handle_webhook(self, request): return res.to_json() - def handle_keyboard_webhook(self): + + def _handle_keyboard_webhook(self): res = KeyboardPayload() if self._keyboard_callback: @@ -95,6 +133,7 @@ def wrapper(func): return wrapper + def get_message_callback(self, req): callback = None diff --git a/tests/test_blueprint.py b/tests/test_blueprint.py new file mode 100644 index 0000000..3733320 --- /dev/null +++ b/tests/test_blueprint.py @@ -0,0 +1,51 @@ +import json +import unittest + +from kakaoplus import kakaoplus as KaKao +from flask import Flask, Blueprint + +class KaKaoTest(unittest.TestCase): + def setUp(self): + blueprint = Blueprint('blueprint_api', __name__, url_prefix="/test_prefix") + self.agent = KaKao.KaKaoAgent(blueprint=blueprint) + + app = Flask(__name__) + app.register_blueprint(blueprint) + self.app = app + + def test_handle_keyboard(self): + with self.app.test_client() as c: + rv = c.get('/test_prefix/keyboard') + self.assertEqual( + json.loads(rv.get_data(as_text=True)), + { + "type": "text" + }) + + def test_handle_message(self): + req = {"user_key": "testID", "type": "text", "content": "test"} + with self.app.test_client() as c: + rv = c.post('/test_prefix/message', data=json.dumps(req), + content_type = 'application/json') + self.assertEqual(rv.get_data(as_text=True), "ok") + + def test_second_blueprint(self): + blueprint2 = Blueprint('blueprint_api2', __name__, url_prefix='/test_prefix_2') + self.agent2 = KaKao.KaKaoAgent(blueprint=blueprint2) + self.app.register_blueprint(blueprint2) + + # test blueprint2 keyboard handler + with self.app.test_client() as c: + rv = c.get('/test_prefix_2/keyboard') + self.assertEqual( + json.loads(rv.get_data(as_text=True)), + { + "type": "text" + }) + + # test blueprint2 message handler + req = {"user_key": "testID", "type": "text", "content": "test"} + with self.app.test_client() as c: + rv = c.post('/test_prefix_2/message', data=json.dumps(req), + content_type='application/json') + self.assertEqual(rv.get_data(as_text=True), "ok") \ No newline at end of file diff --git a/tests/test_kakao.py b/tests/test_kakao.py index 98602e9..4c493d1 100644 --- a/tests/test_kakao.py +++ b/tests/test_kakao.py @@ -4,14 +4,15 @@ import mock from kakaoplus import kakaoplus as KaKao - +from flask import Flask class KaKaoTest(unittest.TestCase): def setUp(self): - self.agent = KaKao.KaKaoAgent() + self.app = Flask(__name__) + self.agent = KaKao.KaKaoAgent(self.app, '/') def test_handle_webhook(self): - req = json.dumps({"user_key": "testID", "type": "text", "content": "test"}) + req = {"user_key": "testID", "type": "text", "content": "test"} counter = mock.MagicMock() @self.agent.handle_message @@ -42,35 +43,37 @@ def default_handler(req, res): ] counter() - res = self.agent.handle_webhook(req) - self.assertEquals( - res, - json.dumps({ - "message": { - "message_button": { - "label": "주유 쿠폰받기", - "url": "https://coupon/url" - }, - "photo": { - "url": "https://photo.src", - "width": 640, - "height": 480 + with self.app.test_client() as c: + rv = c.post('/message', data=json.dumps(req), + content_type='application/json') + self.assertEquals( + rv.get_data(as_text=True), + json.dumps({ + "message": { + "message_button": { + "label": "주유 쿠폰받기", + "url": "https://coupon/url" + }, + "photo": { + "url": "https://photo.src", + "width": 640, + "height": 480 + }, + "text": "귀하의 차량이 성공적으로 등록되었습니다. 축하합니다!" }, - "text": "귀하의 차량이 성공적으로 등록되었습니다. 축하합니다!" - }, - "keyboard": { - "buttons": [ - "처음으로", - "다시 등록하기", - "취소하기" - ], - "type": "buttons" - } - }, sort_keys=True) - ) - self.assertEquals(1, counter.call_count) - - req2 = json.dumps({"user_key": "testID", "type": "text", "content": "hi my name is yo"}) + "keyboard": { + "buttons": [ + "처음으로", + "다시 등록하기", + "취소하기" + ], + "type": "buttons" + } + }, sort_keys=True) + ) + self.assertEquals(1, counter.call_count) + + req2 = {"user_key": "testID", "type": "text", "content": "hi my name is yo"} counter2 = mock.MagicMock() @self.agent.handle_message(['(hi).*']) @@ -83,15 +86,17 @@ def greeting_test(req, res): res.text = 'Regular Expression start with hi' counter2() - res = self.agent.handle_webhook(req2) - self.assertEquals(1, counter2.call_count) - self.assertEqual( - res, - json.dumps({ - 'keyboard': {'type': 'text'}, - 'message': {'text': 'Regular Expression start with hi'} - }, sort_keys=True) - ) + with self.app.test_client() as c: + rv = c.post('/message', data=json.dumps(req2), + content_type='application/json') + self.assertEquals(1, counter2.call_count) + self.assertEqual( + rv.get_data(as_text=True), + json.dumps({ + 'keyboard': {'type': 'text'}, + 'message': {'text': 'Regular Expression start with hi'} + }, sort_keys=True) + ) def test_handle_keyboard(self): @self.agent.handle_keyboard @@ -102,31 +107,30 @@ def keyboard_handler(res): 'test button2', 'test button3' ] - - res = self.agent.handle_keyboard_webhook() - self.assertEqual( - res, - json.dumps({ - "buttons": [ - 'test button1', - 'test button2', - 'test button3' - ], - "type": "buttons" - }, sort_keys=True) - ) + with self.app.test_client() as c: + rv = c.get('/keyboard') + self.assertEqual( + json.loads(rv.get_data(as_text=True)), + { + "buttons": [ + 'test button1', + 'test button2', + 'test button3' + ], + "type": "buttons" + }) def test_handle_text_keyboard(self): - res = self.agent.handle_keyboard_webhook() - self.assertEqual( - res, - json.dumps({ - "type": "text" - }) - ) + with self.app.test_client() as c: + rv = c.get('/keyboard') + self.assertEqual( + json.loads(rv.get_data(as_text=True)), + { + "type": "text" + }) def test_photo_handler(self): - req = json.dumps({"user_key": "testID", "type": "photo", "content": "image.png"}) + req = {"user_key": "testID", "type": "photo", "content": "image.png"} counter = mock.MagicMock() @self.agent.handle_photo def photo_handler(req, res): @@ -140,15 +144,21 @@ def photo_handler(req, res): self.assertIsNone(res.keyboard_buttons) counter() - res = self.agent.handle_webhook(req) - self.assertEqual(res, 'ok') + with self.app.test_client() as c: + rv = c.post('/message', data=json.dumps(req), + content_type='application/json') + self.assertEqual(rv.get_data(as_text=True), 'ok') def test_no_handler(self): - req = json.dumps({"user_key": "testID", "type": "text", "content": "test"}) - res = self.agent.handle_webhook(req) - self.assertEqual(res, "ok") + req = {"user_key": "testID", "type": "text", "content": "test"} + with self.app.test_client() as c: + rv = c.post('/message', data=json.dumps(req), + content_type='application/json') + self.assertEqual(rv.get_data(as_text=True), "ok") def test_no_matching_type(self): - req = json.dumps({"user_key": "testID", "type": "unknown"}) - res = self.agent.handle_webhook(req) - self.assertEqual(res, "ok") \ No newline at end of file + req = {"user_key": "testID", "type": "unknown"} + with self.app.test_client() as c: + rv = c.post('/message', data=json.dumps(req), + content_type='application/json') + self.assertEqual(rv.get_data(as_text=True), "ok") \ No newline at end of file