Skip to content

Commit 1c65ebb

Browse files
authored
Merge pull request #1853 from sopel-irc/mock-http-tests
module, tests: set up `VCR.py` for plugin example tests
2 parents b632876 + 95f37ab commit 1c65ebb

18 files changed

+787
-19
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ quality:
66
test:
77
coverage run -m py.test -v .
88

9+
test_novcr:
10+
coverage run -m py.test -v . --vcr-record=none
11+
912
coverage_report:
1013
coverage report
1114

@@ -16,7 +19,7 @@ coverages: coverage_report coverage_html
1619

1720
qa: quality test coverages
1821

19-
travis: quality test coverage_report
22+
travis: quality test_novcr coverage_report
2023

2124
clean_docs:
2225
$(MAKE) -C docs clean

conftest.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
import pytest
24

35
# This file lists files which should be ignored by pytest
@@ -24,3 +26,20 @@ def pytest_configure(config):
2426
'markers',
2527
'online: for tests that require online access. '
2628
'Use --offline to skip them.')
29+
30+
31+
@pytest.fixture(scope='module')
32+
def vcr_cassette_dir(request):
33+
# Override VCR.py cassette save location, to keep them out of code folders
34+
parts = request.module.__name__.split('.')
35+
if parts[0] == 'sopel':
36+
# We know it's part of Sopel...
37+
parts = parts[1:]
38+
return os.path.join('test', 'vcr', *parts)
39+
40+
41+
@pytest.fixture
42+
def vcr_cassette_path(request, vcr_cassette_name):
43+
# pytest-vcr 0.3.0 looks for this fixture name
44+
# remove when killing off Python 3.3 support
45+
return os.path.join(vcr_cassette_dir(request), vcr_cassette_name)

dev-requirements.txt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
pytest<3.3; python_version == '3.3'
2-
pytest>=4.6,<4.7; python_version != '3.3'
31
coveralls
42
flake8<3.6.0; python_version == '3.3'
53
flake8>=3.7.0,<3.8.0; python_version != '3.3'
64
flake8-coding
75
flake8-future-import<0.4.6
86
flake8-import-order; python_version > '3.3'
97
flake8-import-order<=1.18.1; python_version <= '3.3'
8+
pytest<3.3; python_version == '3.3'
9+
pytest>=4.6,<4.7; python_version != '3.3'
10+
pytest-vcr==1.0.2; python_version != '3.3'
11+
pytest-vcr==0.3.0; python_version == '3.3'
12+
PyYAML<5.1; python_version == '3.3'
13+
PyYAML<5.3; python_version == '3.4'
1014
setuptools<40.0; python_version == '3.3'
1115
sphinx
1216
sphinxcontrib-autoprogram
17+
vcrpy==2.1.1; python_version == '2.7'
18+
vcrpy<1.12.0; python_version == '3.3'
19+
vcrpy<2.1.0; python_version == '3.4'
20+
vcrpy<3.0.0; python_version >= '3.5'

sopel/module.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -736,9 +736,11 @@ class example(object):
736736
:param bool user_help: whether this example should be included in
737737
user-facing help output such as `.help command`
738738
(optional; default ``False``; see below)
739-
:param bool online: if ``True``, |pytest|_ will mark this
740-
example as "online" (optional; default ``False``; see
741-
below)
739+
:param bool online: if ``True``, |pytest|_ will mark this example as
740+
"online" (optional; default ``False``; see below)
741+
:param bool vcr: if ``True``, this example's HTTP requests & responses will
742+
be recorded for later reuse (optional; default ``False``;
743+
see below)
742744
743745
.. |pytest| replace:: ``pytest``
744746
.. _pytest: https://pypi.org/project/pytest/
@@ -766,13 +768,21 @@ class example(object):
766768
can override this choice or include multiple examples by passing
767769
``user_help=True`` to one or more ``example`` decorator(s).
768770
769-
Finally, passing ``online=True`` makes that particular example skippable if
770-
Sopel's test suite is run in offline mode, which is mostly useful to make
771-
life easier for other developers working on Sopel without Internet access.
771+
Passing ``online=True`` makes that particular example skippable if Sopel's
772+
test suite is run in offline mode, which is mostly useful to make life
773+
easier for other developers working on Sopel without Internet access.
774+
775+
Finally, ``vcr=True`` records the example's HTTP requests and responses for
776+
replaying during later test runs. It can be an alternative (or complement)
777+
to ``online``, and is especially useful for testing plugin code that calls
778+
on inconsistent or flaky remote APIs. The recorded "cassettes" of responses
779+
can be committed alongside the code for use by CI services, etc. (See
780+
`VCR.py <https://github.com/kevin1024/vcrpy>`_ & `pytest-vcr
781+
<https://github.com/ktosiek/pytest-vcr>`_)
772782
"""
773783
def __init__(self, msg, result=None, privmsg=False, admin=False,
774784
owner=False, repeat=1, re=False, ignore=None,
775-
user_help=False, online=False):
785+
user_help=False, online=False, vcr=False):
776786
# Wrap result into a list for get_example_test
777787
if isinstance(result, list):
778788
self.result = result
@@ -787,6 +797,7 @@ def __init__(self, msg, result=None, privmsg=False, admin=False,
787797
self.owner = owner
788798
self.repeat = repeat
789799
self.online = online
800+
self.vcr = vcr
790801

791802
if isinstance(ignore, list):
792803
self.ignore = ignore
@@ -820,6 +831,9 @@ def __call__(self, func):
820831
if self.online:
821832
test = pytest.mark.online(test)
822833

834+
if self.vcr:
835+
test = pytest.mark.vcr(test)
836+
823837
sopel.test_tools.insert_into_module(
824838
test, func.__module__, func.__name__, 'test_example'
825839
)

sopel/modules/currency.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,10 @@ def exchange(bot, match):
192192
@commands('cur', 'currency', 'exchange')
193193
@example('.cur 100 usd in btc cad eur',
194194
r'100\.0 USD is [\d\.]+ BTC, [\d\.]+ CAD, [\d\.]+ EUR',
195-
re=True)
195+
re=True, online=True)
196196
@example('.cur 100 usd in btc cad eur can aux',
197197
r'100\.0 USD is [\d\.]+ BTC, [\d\.]+ CAD, [\d\.]+ EUR, \(unsupported: CAN, AUX\)',
198-
re=True)
198+
re=True, online=True)
199199
def exchange_cmd(bot, trigger):
200200
if not trigger.group(2):
201201
return bot.reply("No search term. Usage: {}cur 100 usd in btc cad eur"

sopel/modules/isup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ def isup_insecure(bot, trigger):
7777

7878

7979
@module.commands('isup')
80+
@module.example('.isup google.com',
81+
'http://google.com looks fine to me.',
82+
online=True, vcr=True)
8083
def isup(bot, trigger):
8184
"""Check if a website is up or not."""
8285
handle_isup(bot, trigger, secure=True)

sopel/modules/py.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def setup(bot):
4949

5050
@module.commands('py')
5151
@module.output_prefix('[py] ')
52-
@module.example('.py len([1,2,3])', '3', online=True)
52+
@module.example('.py len([1,2,3])', '3', online=True, vcr=True)
5353
def py(bot, trigger):
5454
"""Evaluate a Python expression."""
5555
if not trigger.group(2):

sopel/modules/search.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ def duck_api(query):
7979
'.duck site:grandorder.wiki chulainn alter',
8080
r'https?:\/\/grandorder\.wiki\/C%C3%BA_Chulainn.*',
8181
re=True,
82-
online=True)
82+
online=True,
83+
vcr=True)
8384
# the last example (in source line order) is what .help displays
8485
@example(
8586
'.duck sopel.chat irc bot',
8687
r'https?:\/\/(sopel\.chat\/?|github\.com\/sopel-irc\/sopel)',
8788
re=True,
88-
online=True)
89+
online=True,
90+
vcr=True)
8991
def duck(bot, trigger):
9092
"""Queries DuckDuckGo for the specified input."""
9193
query = trigger.group(2)

sopel/modules/translate.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ def tr(bot, trigger):
115115
@commands('translate', 'tr')
116116
@example('.tr :en :fr my dog',
117117
'"mon chien" (en to fr, translate.google.com)',
118-
online=True)
119-
@example('.tr מחשב', '"computer" (iw to en, translate.google.com)', online=True)
118+
online=True, vcr=True)
119+
@example('.tr מחשב',
120+
'"computer" (iw to en, translate.google.com)',
121+
online=True, vcr=True)
120122
@example('.tr mon chien',
121123
'"my dog" (fr to en, translate.google.com)',
122-
online=True)
124+
online=True, vcr=True)
123125
def tr2(bot, trigger):
124126
"""Translates a phrase, with an optional language hint."""
125127
command = trigger.group(2)

sopel/modules/url.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def shutdown(bot):
146146
@module.example(
147147
'.title https://www.google.com',
148148
'[ Google ] - www.google.com',
149-
online=True)
149+
online=True, vcr=True)
150150
def title_command(bot, trigger):
151151
"""
152152
Show the title or URL information for the given URL, or the last URL seen

0 commit comments

Comments
 (0)