From a972494bc3a5041b3d655211572f44e204915364 Mon Sep 17 00:00:00 2001 From: Juan Madurga Date: Thu, 10 Mar 2016 13:01:59 +0100 Subject: [PATCH] request: headers and url parameters --- .../migrations/0003_auto_20160310_0941.py | 44 +++++++++++ microbot/models/__init__.py | 2 +- microbot/models/handler.py | 61 +++++++++++++-- microbot/test/factories/__init__.py | 2 +- microbot/test/factories/handler.py | 18 ++++- requirements/test.txt | 3 +- tests/test_microbot.py | 77 +++++++++++++++---- tests/views.py | 8 +- 8 files changed, 183 insertions(+), 32 deletions(-) create mode 100644 microbot/migrations/0003_auto_20160310_0941.py diff --git a/microbot/migrations/0003_auto_20160310_0941.py b/microbot/migrations/0003_auto_20160310_0941.py new file mode 100644 index 0000000..5ec45ce --- /dev/null +++ b/microbot/migrations/0003_auto_20160310_0941.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('microbot', '0002_environmentvar'), + ] + + operations = [ + migrations.CreateModel( + name='HeaderParam', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('key', models.CharField(max_length=255, verbose_name='Key')), + ('value_template', models.CharField(max_length=255, verbose_name='Value template')), + ('request', models.ForeignKey(related_name='header_parameters', verbose_name='Request', to='microbot.Bot')), + ], + options={ + 'verbose_name': 'Header Parameter', + 'verbose_name_plural': 'Header Parameters', + }, + ), + migrations.CreateModel( + name='UrlParam', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('key', models.CharField(max_length=255, verbose_name='Key')), + ('value_template', models.CharField(max_length=255, verbose_name='Value template')), + ('request', models.ForeignKey(related_name='url_parameters', verbose_name='Request', to='microbot.Bot')), + ], + options={ + 'verbose_name': 'Url Parameter', + 'verbose_name_plural': 'Url Parameters', + }, + ), + migrations.RemoveField( + model_name='request', + name='content_type', + ), + ] diff --git a/microbot/models/__init__.py b/microbot/models/__init__.py index b28a796..6bb3b1b 100644 --- a/microbot/models/__init__.py +++ b/microbot/models/__init__.py @@ -1,4 +1,4 @@ from microbot.models.telegram_api import (User, Chat, Message, Update) # NOQA from microbot.models.bot import Bot # NOQA -from microbot.models.handler import Handler, Request # NOQA +from microbot.models.handler import Handler, Request, UrlParam, HeaderParam # NOQA from microbot.models.environment_vars import EnvironmentVar # NOQA \ No newline at end of file diff --git a/microbot/models/handler.py b/microbot/models/handler.py index fd06310..40d689f 100644 --- a/microbot/models/handler.py +++ b/microbot/models/handler.py @@ -11,6 +11,23 @@ logger = logging.getLogger(__name__) + +class AbstractParam(models.Model): + key = models.CharField(_('Key'), max_length=255) + value_template = models.CharField(_('Value template'), max_length=255) + + class Meta: + abstract = True + verbose_name = _('Parameter') + verbose_name_plural = _('Parameters') + + def __str__(self): + return "(%s, %s)" % (self.key, self.value_template) + + def process(self, **context): + value_template = Template(self.value_template) + return value_template.render(**context) + @python_2_unicode_compatible class Request(models.Model): url_template = models.CharField(_('Url template'), max_length=255) @@ -22,7 +39,6 @@ class Request(models.Model): (DELETE, _("Delete")), ) method = models.CharField(_("Method"), max_length=128, default=GET, choices=METHOD_CHOICES) - content_type = models.CharField(_('Content type'), max_length=255, null=True, blank=True) data = models.TextField(null=True, blank=True, verbose_name=_("Data of the request")) class Meta: @@ -32,21 +48,50 @@ class Meta: def __str__(self): return "%s(%s)" % (self.method, self.url_template) + def _url_params(self, **context): + params = {} + for param in self.url_parameters.all(): + params[param.key] = param.process(**context) + return params + + def _header_params(self, **context): + headers = {} + for header in self.header_parameters.all(): + headers[header.key] = header.process(**context) + return headers + def process(self, **context): url_template = Template(self.url_template) url = url_template.render(**context) - logger.debug("Request %s generates url %s" % (self, url)) + logger.debug("Request %s generates url %s" % (self, url)) + params = self._url_params(**context) + logger.debug("Request %s generates params %s" % (self, params)) + headers = self._header_params(**context) + logger.debug("Request %s generates header %s" % (self, headers)) + if self.method == self.GET: - r = requests.get(url) + r = requests.get(url, headers=headers, params=params) elif self.method == self.POST: - headers = {'content_type': self.content_type} - r = requests.post(url, json.loads(self.data), headers=headers) + r = requests.post(url, data=json.loads(self.data), headers=headers, params=params) elif self.method == self.PUT: - headers = {'content_type': self.content_type} - r = requests.put(url, json.loads(self.data), headers=headers) + r = requests.put(url, data=json.loads(self.data), headers=headers, params=params) else: - r = requests.delete(url) + r = requests.delete(url, headers=headers, params=params) return r + +class UrlParam(AbstractParam): + request = models.ForeignKey(Request, verbose_name=_('Request'), related_name="url_parameters") + + class Meta: + verbose_name = _("Url Parameter") + verbose_name_plural = _("Url Parameters") + +class HeaderParam(AbstractParam): + request = models.ForeignKey(Request, verbose_name=_('Request'), related_name="header_parameters") + + class Meta: + verbose_name = _("Header Parameter") + verbose_name_plural = _("Header Parameters") @python_2_unicode_compatible diff --git a/microbot/test/factories/__init__.py b/microbot/test/factories/__init__.py index 6d86ace..0f765d3 100644 --- a/microbot/test/factories/__init__.py +++ b/microbot/test/factories/__init__.py @@ -1,4 +1,4 @@ from microbot.test.factories.bot import BotFactory # noqa from microbot.test.factories.telegram_lib import (UserLibFactory, ChatLibFactory, # noqa MessageLibFactory, UpdateLibFactory) # noqa -from microbot.test.factories.handler import HandlerFactory, RequestFactory # noqa \ No newline at end of file +from microbot.test.factories.handler import HandlerFactory, RequestFactory, UrlParamFactory, HeaderParamFactory # noqa \ No newline at end of file diff --git a/microbot/test/factories/handler.py b/microbot/test/factories/handler.py index 76f4697..eaaea2b 100644 --- a/microbot/test/factories/handler.py +++ b/microbot/test/factories/handler.py @@ -1,6 +1,6 @@ # coding=utf-8 -from factory import DjangoModelFactory, SubFactory -from microbot.models import Handler, Request +from factory import DjangoModelFactory, SubFactory, Sequence +from microbot.models import Handler, Request, UrlParam, HeaderParam from microbot.test.factories import BotFactory @@ -9,6 +9,20 @@ class Meta: model = Request url_template = "https://api.github.com/users/jlmadurga" method = Request.GET + +class UrlParamFactory(DjangoModelFactory): + class Meta: + model = UrlParam + key = Sequence(lambda n: 'key%d' % n) + value_template = Sequence(lambda n: '{{value%d}}' % n) + request = SubFactory(RequestFactory) + +class HeaderParamFactory(DjangoModelFactory): + class Meta: + model = HeaderParam + key = Sequence(lambda n: 'key%d' % n) + value_template = Sequence(lambda n: '{{value%d}}' % n) + request = SubFactory(RequestFactory) class HandlerFactory(DjangoModelFactory): diff --git a/requirements/test.txt b/requirements/test.txt index 0f68bcf..d51b737 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,4 +3,5 @@ coverage mock==1.3.0 flake8>=2.1.0 tox>=1.7.0 -factory-boy==2.6.1 \ No newline at end of file +factory-boy==2.6.1 +django-filter==0.12.0 \ No newline at end of file diff --git a/tests/test_microbot.py b/tests/test_microbot.py index 777abad..9720b81 100644 --- a/tests/test_microbot.py +++ b/tests/test_microbot.py @@ -11,9 +11,8 @@ except ImportError: import mock # noqa - class TestBot(testcases.BaseTestBot): - + def test_enable_webhook(self): self.assertTrue(self.bot.enabled) with mock.patch("telegram.bot.Bot.setWebhook", callable=mock.MagicMock()) as mock_setwebhook: @@ -22,7 +21,7 @@ def test_enable_webhook(self): self.assertEqual(1, mock_setwebhook.call_count) self.assertIn(reverse('microbot:telegrambot', kwargs={'token': self.bot.token}), kwargs['webhook_url']) - + def test_disable_webhook(self): self.bot.enabled = False with mock.patch("telegram.bot.Bot.setWebhook", callable=mock.MagicMock()) as mock_setwebhook: @@ -30,26 +29,26 @@ def test_disable_webhook(self): args, kwargs = mock_setwebhook.call_args self.assertEqual(1, mock_setwebhook.call_count) self.assertEqual(None, kwargs['webhook_url']) - + def test_bot_user_api(self): with mock.patch("telegram.bot.Bot.setWebhook", callable=mock.MagicMock()): self.bot.user_api = None self.bot.save() self.assertEqual(self.bot.user_api.first_name, u'Microbot_test') self.assertEqual(self.bot.user_api.username, u'Microbot_test_bot') - + def test_no_bot_associated(self): Bot.objects.all().delete() self.assertEqual(0, Bot.objects.count()) response = self.client.post(self.webhook_url, self.update.to_json(), **self.kwargs) self.assertEqual(status.HTTP_404_NOT_FOUND, response.status_code) - + def test_bot_disabled(self): self.bot.enabled = False self.bot.save() response = self.client.post(self.webhook_url, self.update.to_json(), **self.kwargs) self.assertEqual(status.HTTP_404_NOT_FOUND, response.status_code) - + def test_not_valid_update(self): del self.update.message response = self.client.post(self.webhook_url, self.update.to_json(), **self.kwargs) @@ -123,6 +122,20 @@ class TestRequests(LiveServerTestCase, testcases.BaseTestBot): } } + author_get_with_url_parameters = {'in': '/authors_name@author1', + 'out': {'parse_mode': 'HTML', + 'reply_markup': '', + 'text': 'author1' + } + } + + author_post_header_error = {'in': '/authors', + 'out': {'parse_mode': 'HTML', + 'reply_markup': '', + 'text': 'not created' + } + } + def test_get_request(self): Author.objects.create(name="author1") self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/', @@ -133,7 +146,7 @@ def test_get_request(self): response_text_template='{% for author in response.list %}{{author.name}}{% endfor %}', response_keyboard_template='') self._test_message(self.author_get) - + def test_get_pattern_command(self): Author.objects.create(name="author1") self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/{{url.id}}/', @@ -144,7 +157,7 @@ def test_get_pattern_command(self): response_keyboard_template='', request=self.request) self._test_message(self.author_get_pattern) - + def test_get_request_with_keyboard(self): Author.objects.create(name="author1") self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/', @@ -155,11 +168,10 @@ def test_get_request_with_keyboard(self): response_text_template='{% for author in response.list %}{{author.name}}{% endfor %}', response_keyboard_template='[[{% for author in response.list %}"{{author.name}}"{% endfor %}]]') self._test_message(self.author_get_keyboard) - + def test_post_request(self): self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/', method=Request.POST, - content_type="application/json", data='{"name": "author1"}') self.handler = factories.HandlerFactory(bot=self.bot, pattern='/authors', @@ -170,12 +182,11 @@ def test_post_request(self): self.assertEqual(Author.objects.count(), 1) author = Author.objects.all()[0] self.assertEqual(author.name, "author1") - + def test_put_request(self): author = Author.objects.create(name="author1") self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/{{url.id}}/', method=Request.PUT, - content_type="application/json", data='{"name": "author2"}') self.handler = factories.HandlerFactory(bot=self.bot, pattern='/authors@(?P\d+)', @@ -186,7 +197,7 @@ def test_put_request(self): self.assertEqual(Author.objects.count(), 1) author = Author.objects.all()[0] self.assertEqual(author.name, "author2") - + def test_delete_request(self): Author.objects.create(name="author1") self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/{{url.id}}/', @@ -198,7 +209,7 @@ def test_delete_request(self): response_keyboard_template='') self._test_message(self.author_delete_pattern) self.assertEqual(Author.objects.count(), 0) - + def test_environment_vars(self): EnvironmentVar.objects.create(bot=self.bot, key="shop", @@ -211,4 +222,38 @@ def test_environment_vars(self): request=self.request, response_text_template='{{env.shop}}:{% for author in response.list %}{{author.name}}{% endfor %}', response_keyboard_template='') - self._test_message(self.author_get_with_environment_var) \ No newline at end of file + self._test_message(self.author_get_with_environment_var) + + def test_url_parameters(self): + Author.objects.create(name="author1") + Author.objects.create(name="author2") + self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/', + method=Request.GET) + self.url_param = factories.UrlParamFactory(request=self.request, + key='name', + value_template='{{url.name}}') + self.handler = factories.HandlerFactory(bot=self.bot, + pattern='/authors_name@(?P\w+)', + request=self.request, + response_text_template='{% for author in response.list %}{{author.name}}{% endfor %}', + response_keyboard_template='') + self._test_message(self.author_get_with_url_parameters) + + def test_header_parameters(self): + # Unsupported media type 415. Author not created + EnvironmentVar.objects.create(bot=self.bot, + key="content_type", + value="application/xml") + self.request = factories.RequestFactory(url_template=self.live_server_url + '/api/authors/', + method=Request.POST, + data='{"name": "author1"}') + self.header_param = factories.HeaderParamFactory(request=self.request, + key='Content-Type', + value_template='{{env.content_type}}') + self.handler = factories.HandlerFactory(bot=self.bot, + pattern='/authors', + request=self.request, + response_text_template='{% if response.name %}{{response.name}} created{% else %}not created{% endif %}', + response_keyboard_template='') + self._test_message(self.author_post_header_error) + self.assertEqual(Author.objects.count(), 0) \ No newline at end of file diff --git a/tests/views.py b/tests/views.py index 59aad78..43d1311 100644 --- a/tests/views.py +++ b/tests/views.py @@ -1,10 +1,12 @@ -from rest_framework import viewsets +from rest_framework import viewsets, filters from tests.serializers import AuthorSerializer from tests.models import Author class AuthorViewSet(viewsets.ModelViewSet): """ - API endpoint that allows users to be viewed or edited. + API endpoint that allows authors to be viewed or edited. """ queryset = Author.objects.all() - serializer_class = AuthorSerializer \ No newline at end of file + serializer_class = AuthorSerializer + filter_backends = (filters.DjangoFilterBackend,) + filter_fields = ('name', 'id') \ No newline at end of file