Skip to content

Commit efd1e7c

Browse files
author
Jon Wayne Parrott
committed
Merge pull request GoogleCloudPlatform#29 from GoogleCloudPlatform/pytest-refactor-step-5
Updating 5-logging to use py.test
2 parents 5463065 + a1a1a89 commit efd1e7c

File tree

6 files changed

+252
-161
lines changed

6 files changed

+252
-161
lines changed

5-logging/requirements-dev.txt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
tox==2.3.1
2-
unittest2==1.1.0
3-
nose==1.3.7
42
flake8==2.5.4
5-
coverage==4.1b2
6-
BeautifulSoup4==4.4.1
7-
mock==1.3.0
8-
flaky==3.0.3
3+
flaky==3.1.0
4+
pytest==2.8.7
5+
pytest-cov==2.2.1

5-logging/tests/conftest.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2015 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""conftest.py is used to define common test fixtures for pytest."""
16+
17+
import bookshelf
18+
import config
19+
from gcloud.exceptions import ServiceUnavailable
20+
import pytest
21+
22+
23+
@pytest.yield_fixture(params=['datastore', 'cloudsql', 'mongodb'])
24+
def app(request):
25+
"""This fixtures provides a Flask app instance configured for testing.
26+
27+
Because it's parametric, it will cause every test that uses this fixture
28+
to run three times: one time for each backend (datastore, cloudsql, and
29+
mongodb).
30+
31+
It also ensures the tests run within a request context, allowing
32+
any calls to flask.request, flask.current_app, etc. to work."""
33+
app = bookshelf.create_app(
34+
config,
35+
testing=True,
36+
config_overrides={
37+
'DATA_BACKEND': request.param
38+
})
39+
40+
with app.test_request_context():
41+
yield app
42+
43+
44+
@pytest.yield_fixture
45+
def model(monkeypatch, app):
46+
"""This fixture provides a modified version of the app's model that tracks
47+
all created items and deletes them at the end of the test.
48+
49+
Any tests that directly or indirectly interact with the database should use
50+
this to ensure that resources are properly cleaned up.
51+
52+
Monkeypatch is provided by pytest and used to patch the model's create
53+
method.
54+
55+
The app fixture is needed to provide the configuration and context needed
56+
to get the proper model object.
57+
"""
58+
model = bookshelf.get_model()
59+
60+
ids_to_delete = []
61+
62+
# Monkey-patch create so we can track the IDs of every item
63+
# created and delete them after the test case.
64+
original_create = model.create
65+
66+
def tracking_create(*args, **kwargs):
67+
res = original_create(*args, **kwargs)
68+
ids_to_delete.append(res['id'])
69+
return res
70+
71+
monkeypatch.setattr(model, 'create', tracking_create)
72+
73+
yield model
74+
75+
# Delete all items that we created during tests.
76+
list(map(model.delete, ids_to_delete))
77+
78+
79+
def flaky_filter(info, *args):
80+
"""Used by flaky to determine when to re-run a test case."""
81+
_, e, _ = info
82+
return isinstance(e, ServiceUnavailable)

5-logging/tests/test_auth.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright 2015 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import contextlib
16+
17+
from conftest import flaky_filter
18+
from flaky import flaky
19+
from oauth2client.client import OAuth2Credentials
20+
import pytest
21+
22+
23+
@pytest.fixture
24+
def client_with_credentials(app):
25+
"""This fixture provides a Flask app test client that has a session
26+
pre-configured with use credentials."""
27+
credentials = OAuth2Credentials(
28+
'access_token',
29+
'client_id',
30+
'client_secret',
31+
'refresh_token',
32+
'3600',
33+
None,
34+
'Test',
35+
id_token={'sub': '123', 'email': 'user@example.com'},
36+
scopes=('email', 'profile'))
37+
38+
@contextlib.contextmanager
39+
def inner():
40+
with app.test_client() as client:
41+
with client.session_transaction() as session:
42+
session['profile'] = {'id': 'abc', 'displayName': 'Test User'}
43+
session['google_oauth2_credentials'] = credentials.to_json()
44+
yield client
45+
46+
return inner
47+
48+
49+
# Mark all test cases in this class as flaky, so that if errors occur they
50+
# can be retried. This is useful when databases are temporarily unavailable.
51+
@flaky(rerun_filter=flaky_filter)
52+
# Tell pytest to use both the app and model fixtures for all test cases.
53+
# This ensures that configuration is properly applied and that all database
54+
# resources created during tests are cleaned up. These fixtures are defined
55+
# in conftest.py
56+
@pytest.mark.usefixtures('app', 'model')
57+
class TestAuth(object):
58+
59+
def test_not_logged_in(self, app):
60+
with app.test_client() as c:
61+
rv = c.get('/books/')
62+
63+
assert rv.status == '200 OK'
64+
body = rv.data.decode('utf-8')
65+
assert 'Login' in body
66+
67+
def test_logged_in(self, client_with_credentials):
68+
with client_with_credentials() as c:
69+
rv = c.get('/books/')
70+
71+
assert rv.status == '200 OK'
72+
body = rv.data.decode('utf-8')
73+
assert 'Test User' in body
74+
75+
def test_add_anonymous(self, app):
76+
data = {
77+
'title': 'Test Book',
78+
}
79+
80+
with app.test_client() as c:
81+
rv = c.post('/books/add', data=data, follow_redirects=True)
82+
83+
assert rv.status == '200 OK'
84+
body = rv.data.decode('utf-8')
85+
assert 'Test Book' in body
86+
assert 'Added by Anonymous' in body
87+
88+
def test_add_logged_in(self, client_with_credentials):
89+
data = {
90+
'title': 'Test Book',
91+
}
92+
93+
with client_with_credentials() as c:
94+
rv = c.post('/books/add', data=data, follow_redirects=True)
95+
96+
assert rv.status == '200 OK'
97+
body = rv.data.decode('utf-8')
98+
assert 'Test Book' in body
99+
assert 'Added by Test User' in body
100+
101+
def test_mine(self, model, client_with_credentials):
102+
# Create two books, one created by the logged in user and one
103+
# created by another user.
104+
model.create({
105+
'title': 'Book 1',
106+
'createdById': 'abc'
107+
})
108+
109+
model.create({
110+
'title': 'Book 2',
111+
'createdById': 'def'
112+
})
113+
114+
# Check the "My Books" page and make sure only one of the books
115+
# appears.
116+
with client_with_credentials() as c:
117+
rv = c.get('/books/mine')
118+
119+
assert rv.status == '200 OK'
120+
body = rv.data.decode('utf-8')
121+
assert 'Book 1' in body
122+
assert 'Book 2' not in body

0 commit comments

Comments
 (0)