Flask-Testing: issues with session management with unittests - python

Here is my route:
#blueprint.before_request
def load_session_from_cookie():
if request.endpoint != 'client.me':
try:
cookie = request.cookies.get(settings.COOKIE_NAME, None)
# if cookie does not exist, redirect to login url
if not cookie:
session.pop('accountId', None)
return redirect(settings.LOGIN_URL)
account = check_sso_cookie(cookie)
if 'accountId' in session:
return
elif 'accountId' in account:
session['accountId'] = account.get('accountId')
return
else:
session.pop('accountId', None)
return redirect(settings.LOGIN_URL)
except BadSignature:
session.pop('accountId', None)
return redirect(settings.LOGIN_URL)
#blueprint.route('/')
def home():
session.permanent = True
return render_template('index.html')
Here is my test:
from flask import Flask
from flask.ext.testing import TestCase
from api.client import blueprint
class TestInitViews(TestCase):
render_templates = False
def create_app(self):
app = Flask(__name__)
app.config['TESTING'] = True
app.config['SECRET_KEY'] = 'sekrit!'
app.register_blueprint(blueprint)
return app
def setUp(self):
self.app = self.create_app()
self.cookie = '{ "account_id": 100 }'
def test_root_route(self):
resp = self.client.get("/")
self.assert_template_used('index.html')
def test_root_route_404(self):
res = self.client.get('/foo')
self.assertEqual(res.status_code, 404)
The problem is that the test test_root_route fails because a redirect happens because the session doesn't exist. I can't find any good resource online that shows how to incorporate session management with Flask-Tests... anyone have a good way of doing this?

You can make login request before:
def create_app(self):
...
app.config['TESTING'] = True # should off csrf
...
def test_root_route(self):
self.client.post(settings.LOGIN_URL, data={'login': 'l', 'password': 'p'})
resp = self.client.get('/')
self.assert_template_used('index.html')
For some difficult cases login route can be mocked.
Or manually set cookie:
def test_root_route(self):
resp = self.client.get('/', headers={'Cookie': 'accountId=test'})
self.assert_template_used('index.html')
Or set session with session transaction (http://flask.pocoo.org/docs/testing/#accessing-and-modifying-sessions):
def test_root_route(self):
with self.client.session_transaction() as session:
session['accountId'] = 'test'
resp = self.client.get('/')
self.assert_template_used('index.html')

Related

Import Spotify song request

I would like to connect to spotify API and connect with OAuth and then search for a song a user requests.
I currently have a bot pulling for youtube and saving the file to play. I was wondering if there a way to do the same with Spotipy.
from flask import Flask, request, url_for, session, redirect
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import time
app = Flask(__name__)
app.secret_key = 'xxx'
app.config['SESSION_COOKIE_NAME'] = 'Token Cookie'
TOKEN_INFO = "token_info"
#app.route('/')
def login():
sp_oauth = create_spotify_oauth()
auth_url = sp_oauth.get_authorize_url()
return redirect(auth_url)
#app.route('/redirect')
def redirectPage():
sp_oauth = create_spotify_oauth()
session.clear()
code = request.args.get('code')
token_info = sp_oauth.get_access_token(code)
session[TOKEN_INFO] = token_info
return redirect(url_for('getTracks', _external=True))
#app.route('/getTracks')
def getTracks():
try:
token_info = get_token()
except:
print("user not logged in)")
redirect("/")
sp = spotipy.Spotify(auth = token_info["access_token"])
return
def get_token():
token_info = session.get(TOKEN_INFO, None)
if not token_info:
raise "execption"
now = int(time.time())
is_expired = token_info["expires_at"] - now < 60
if (is_expired):
sp_oauth = create_spotify_oauth()
token_info = sp_oauth.refresh_access_token(token_info['refresh_token'])
return token_info
def create_spotify_oauth():
return SpotifyOAuth(
client_id = 'CLIENT_ID',
client_secret = 'CLIENT_SECRET',
redirect_uri = url_for('redirectPage', _external = True),
scope= 'user-library-read')

Setting follow_redirects = True causes pytest to timeout with flask appliction

I have a flask application that I am trying to test.
Right now I have a test that works correctly when I test for an http status code of 302, but when I add in follow_redirects = True I get no response from pytest when I run it and I'm not sure why.
Here's the class of tests that I am trying to run:
class TestEmailResetToken():
def test_email_reset(self, client, auth):
auth.login()
token = 'misshapentoken4856'
response = client.get(f'/confirm/{token}', follow_redirects = True)
assert response.status_code == 200
assert b'Update Your Signin Credentials' in response.data
def test_email_reset_valid_auth(self, app, client, auth):
token_data = {
'new_email': 'newemail#gmail.com',
'current_email': 'myemail#gmail.com'
}
with app.app_context():
token = generate_new_email_token(**token_data)
auth.login()
response = client.get(f'/confirm/{token}', follow_redirects = True)
assert response.status_code == 200
assert b'Email updated successfully' in response.data
with app.app_context():
email = confirm_token(token)
user = User.query.filter_by(email = email['new']).first()
assert user.email == email['new']
def test_email_reset_invalid_auth(self, app, client, auth):
token_data = {
'new_email': 'newemail#gmail.com',
'current_email': 'foo#bar.com'
}
with app.app_context():
token = generate_new_email_token(**token_data)
auth.login()
response = client.get(f'/confirm/{token}', follow_redirects = True)
assert response.status_code == 200
assert b'There was a problem processing your request' in response.data
My problem is that in test_email_reset_invalid_auth removing the follow_redirects option causes the test to complete, but when it's there the test just hangs.
What's more, if I comment out the test above it, test_email_reset_valid_auth, then it will work just fine. So I'm assuming it's in how I have my testing suite setup, rather than the actual unit test itself.
If it helps, here's the contents of my conftest.py file:
from app import create_app
from app.commands import init_db
from app.extensions import db as _db
from app.blueprints.user.models import User
from app.blueprints.user import extras
#pytest.fixture(scope='session')
def app():
"""Starts up a version of the application in testing mode to use for further tests"""
params = {
'DEBUG': False,
'TESTING': True,
'WTF_CSRF_ENABLED': False,
'SQLALCHEMY_DATABASE_URI': settings.SQLALCHEMY_TEST_DATABASE_URI
}
_app = create_app(settings_override=params)
with _app.app_context():
_db.drop_all()
_db.create_all()
# users with different settings for testing
test_users = [{
'email': 'myemail#gmail.com',
'password': 'Passw0rd!',
'sec_question': 'What is your favorite band?',
'sec_answer': 'built to spill'
},{
'email': 'myemail1#hotmail.com',
'password': 'Passw0rd!',
'sec_question': 'What is your favorite band?',
'sec_answer': 'Built to Spill',
'active': True
}]
for user_params in test_users:
user = User(**user_params)
_db.session.add(user)
_db.session.commit()
yield _app
#pytest.fixture(scope='function')
def client(app):
"""Yields a test version of the application"""
return app.test_client()
#pytest.fixture(scope='function')
def runner(app):
"""Yields a test version of the app to test command line functions"""
return app.test_cli_runner()
class Authentication():
"""Allows other test objects to login / logout"""
def __init__(self, client):
self._client = client
def login(self, email='myemail#gmail.com', password = 'Passw0rd!', follow_redirects=True):
return self._client.post('/login', data = {'email': email, 'password': password}, follow_redirects = follow_redirects)
def logout(self, follow_redirects = True):
return self._client.get('/logout', follow_redirects = follow_redirects)
#pytest.fixture(scope='function')
def auth(client):
return Authentication(client)
I just encountered this symptom. The cause of the failure was improper use of the url_for function as part of a redirect.
The offending code looked something like this:
redirect(url_for('route.index') + "/some/" + id + "/path")
and needed to be converted into something like this:
redirect(url_for('route.some_path_func', id=id))
I can't tell from the OP's code whether this is their exact problem, however almost certainly whatever is causing this symptom is a problem in the code being tested and not in the test code.

Securing endpoints in fastapi

In my main.py, I have the following code-
app = FastAPI(docs_url="",)
app.add_middleware(SessionMiddleware, secret_key=os.getenv('SECRET'))
config = Config('.env')
oauth = OAuth(config)
CONF_URL = 'http://localhost:9090/.well-known/openid-configuration'
oauth.register(
name='google',
server_metadata_url=CONF_URL,
client_id=os.getenv('ID'),
client_secret=os.getenv('SECRET'),
client_kwargs={
'scope': 'openid email profile'
}
)
api_url = None
#app.get('/')
async def homepage(request: Request):
user = request.session.get('user')
if user:
data = json.dumps(user)
html = (
f'<pre>{data}</pre>'
'logout'
)
return HTMLResponse(html)
return HTMLResponse('login')
#app.get('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri)
#app.get('/auth')
async def auth(request: Request):
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f'<h1>{error.error}</h1>')
user = await oauth.google.parse_id_token(request, token)
request.session['user'] = dict(user)
if api_url:
return RedirectResponse(url=api_url)
return RedirectResponse(url='/')
#app.get('/logout')
async def logout(request: Request):
request.session.pop('user', None)
request.cookies.clear()
return RedirectResponse(url='/')
#app.get("/api")
async def get_swagger_ui(request: Request):
global api_url
api_url = request.url
user = request.session.get('user')
if user:
return get_swagger_ui_html(
openapi_url="/openapi.json", title="Webapp",)
else:
return RedirectResponse(
url="/login"
)
# routes
PROTECTED = [Depends(login)]
app.include_router(get_api.router, dependencies=PROTECTED)
In the get_api.py file, I have the following conf -
router = APIRouter()
#router.get("/api/higs", tags=["higs"])
def get_higs(db: Session = Depends(get_db),
)
try:
<something>
return x
except Exception as err:
raise HTTPException(
status_code=400, detail="Invalid parameter : {}".format(err),
)
There are similar other endpoints in the get_api.py file. I wanted to block access to these endpoints without authentication. So in the app.include_router method, I added dependencies. But its not working. I am able to access the endpoint data. For e.g. localhost:8000/api/higs - displays all the data in text that I would get from calling executing the GET endpoint in swagger UI. How can I fix this issue. Thanks.

How do I work with register_blueprint in Flask?

I have the following app.py:
from flask import Flask
from waitress import serve
from bprint import api_blueprint
from errors import invalid_id, not_found, invalid_input, internal_server_error, unauthorized_access
app = Flask(__name__)
app.register_blueprint(api_blueprint)
app.register_error_handler(400, invalid_id)
app.register_error_handler(401, unauthorized_access)
app.register_error_handler(404, not_found)
app.register_error_handler(405, invalid_input)
app.register_error_handler(500, internal_server_error)
if __name__ == "__main__":
serve(app, host='localhost')
And the following code in bprint.py:
from flask import Blueprint, jsonify, request
import dbu
from models import Session, user_table, car_table, order_table
from schema import UserDetails, UserQuery, OrderDetails, OrderQuery, CarDetails, CarQuery, LoginData, \
ListUsersReq, Response
from contextlib import contextmanager
from flask_jwt_extended import jwt_required, create_access_token, get_jwt_identity
import datetime
api_blueprint = Blueprint('api', __name__)
#contextmanager
def session_scope():
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
else:
try:
session.commit()
except:
session.rollback()
raise
#api_blueprint.route("/login", methods=["POST"])
def login():
from app import bcrypt
data = LoginData().load(request.json)
if data:
user = dbu.get_entry_by_username(user_table, username=data["username"])
hpw = bcrypt.generate_password_hash(data["password"])
if not user:
return jsonify({"message": "Couldn't find user!"})
if bcrypt.check_password_hash(hpw, data["password"]):
access_token = create_access_token(identity=data["username"], expires_delta=datetime.timedelta(days=365))
return jsonify(access_token=access_token, id=user.id), 200
#api_blueprint.route("/user", methods=["GET"])
def list_users():
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin:
args = ListUsersReq().load(request.args)
userlist = dbu.list_users(args.get("email"), args.get("username"))
return jsonify(UserDetails(many=True).dump(userlist))
else:
return jsonify(code=401, type='UNAUTHORIZED_ACCESS'), 401
#api_blueprint.route("/user", methods=["POST"])
def create_user():
with session_scope():
from app import bcrypt
user_details = UserQuery().load(request.get_json(force=True))
user_details["password"] = bcrypt.generate_password_hash(user_details["password"]).decode('UTF-8')
user = dbu.create_entry(user_table, **user_details)
access_token = create_access_token(identity=user.username, expires_delta=datetime.timedelta(days=365))
return jsonify(access_token=access_token, id=UserDetails().dump(user)["id"]), 200
#api_blueprint.route("/user/<int:id>", methods=["GET"])
def user_by_id(id):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin:
user = dbu.get_entry_by_id(user_table, id)
return jsonify(UserDetails().dump(user))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/user/<int:id>", methods=["PUT"])
def update_user(id):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin or user.id == id:
user_details = UserQuery().load(request.json)
user = dbu.get_entry_by_id(user_table, id)
dbu.update_entry(user, **user_details)
return jsonify(Response().dump({"code": "200"}))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/user/<int:id>", methods=["DELETE"])
def delete_user(id):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin or user.id == id:
dbu.delete_entry(user_table, id)
return jsonify(Response().dump({"code": "200"}))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/cars", methods=["GET"])
def get_inventory():
with session_scope():
cars = dbu.list_cars()
return jsonify(CarDetails(many=True).dump(cars))
#api_blueprint.route("/cars/car/<int:carId>", methods=["GET"])
def get_car_by_id(carId):
with session_scope():
car = dbu.get_car_by_id(car_table, carId)
return jsonify(CarDetails().dump(car))
#api_blueprint.route("/cars/car", methods=["POST"])
def create_car():
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin:
car_details = CarQuery().load(request.json)
car = dbu.create_entry(car_table, **car_details)
return jsonify({"carId": CarDetails().dump(car)["carId"]})
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/cars/car/<int:carId>", methods=["PUT"])
def update_car(carId):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin:
car_details = CarQuery().load(request.json)
car = dbu.get_car_by_id(car_table, carId)
dbu.update_entry(car, **car_details)
return jsonify(Response().dump({"code": "200"}))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/cars/car/<int:carId>", methods=["DELETE"])
def delete_car(carId):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin:
dbu.delete_car(car_table, carId)
return jsonify(Response().dump({"code": "200"}))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/cars/car/<int:carId>/order", methods=["POST"])
def place_order(carId):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user:
order_data = OrderQuery().load(request.json)
order = dbu.create_entry(order_table,
userId=user.id,
carId=carId,
shipDate=order_data["shipDate"],
returnDate=order_data["returnDate"],
status="placed",
complete=False)
return jsonify({"id": OrderDetails().dump(order)["id"]})
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/orders", methods=["GET"])
def get_orders():
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user.admin:
orders = dbu.list_orders()
return jsonify(OrderDetails(many=True).dump(orders))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/cars/car/<int:carId>/order/<int:orderId>", methods=["GET"])
def get_order_by_id(carId, orderId):
with session_scope():
current_user = get_jwt_identity()
user = dbu.get_entry_by_username(user_table, current_user)
if user:
order = dbu.get_entry_by_id(order_table, id=orderId)
return jsonify(OrderDetails().dump(order))
else:
return jsonify(code=401, type="UNAUTHORIZED_ACCESS"), 401
#api_blueprint.route("/cars/car/<int:carId>/order/<int:orderId>", methods=["DELETE"])
def delete_order(carId, orderId):
with session_scope():
dbu.delete_entry(order_table, id=orderId)
return jsonify(Response().dump({"code": "200"}))
When I try to run waitress-serve --port=5000 app:app I get the following error:
app.register_blueprint(api_blueprint)
raise AssertionError(
AssertionError: View function mapping is overwriting an existing endpoint function: api.wrapper
What may be the problem?
I'm almost sure it worked in December and now after reinstalling my Windows it doesn't
Now it also says I have too much code in my question and I don't know how to explain my problem with more words, so I have to add a few useless lines, sorry
Check if you have some custom decorators on views. Because flask take endpoint name either from #route parameter or from function name. In your case there're no endpoint parameter in any of functions.
Error says api.wrapper and it means that you have 2 or more function with name wrapper. Usually we see such name inside decorators. So you probably have decorator that looks like
def decorator(f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
And some views are decorated with such decorator. And flask take "wrapper" as a view name
Try by commenting few lines of code - It may help you to resolve your issue.
from flask import Flask
from waitress import serve
from bprint import api_blueprint
# from errors import invalid_id, not_found, invalid_input, internal_server_error, unauthorized_access
app = Flask(__name__)
app.register_blueprint(api_blueprint)
# app.register_error_handler(400, invalid_id)
# app.register_error_handler(401, unauthorized_access)
# app.register_error_handler(404, not_found)
# app.register_error_handler(405, invalid_input)
# app.register_error_handler(500, internal_server_error)
if __name__ == "__main__":
serve(app, host='localhost')

OperationalError: (sqlite3.OperationalError) no such table: user

I'm completely new to flask and web development in general. And what I need is to login to a website using steam id. I'm doing it as it said here, but get the following error:
OperationalError: (sqlite3.OperationalError) no such table: user
It seems to open up steam website correctly but it breaks when I press Log In. So, what's my mistake ? Any help is appreciated.
The code:
from flask import Flask, render_template, redirect, session, json, g
from flask_bootstrap import Bootstrap
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.openid import OpenID
import urllib
import re
app = Flask(__name__)
app.secret_key = '123'
Bootstrap(app)
app.config.from_pyfile('settings.cfg')
db = SQLAlchemy(app)
oid = OpenID(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
steam_id = db.Column(db.String(40))
nickname = db.String(80)
#staticmethod
def get_or_create(steam_id):
rv = User.query.filter_by(steam_id=steam_id).first()
if rv is None:
rv = User()
rv.steam_id = steam_id
db.session.add(rv)
return rv
def get_steam_userinfo(steam_id):
options = {
'key': app.config['STEAM_API_KEY'],
'steamids': steam_id
}
url = 'http://api.steampowered.com/ISteamUser/' \
'GetPlayerSummaries/v0001/?%s' % urllib.urlencode(options)
rv = json.load(urllib.urlopen(url))
return rv['response']['players']['player'][0] or {}
_steam_id_re = re.compile('steamcommunity.com/openid/id/(.*?)$')
#app.route('/login')
#oid.loginhandler
def login():
if g.user is not None:
return redirect(oid.get_next_url())
return oid.try_login('http://steamcommunity.com/openid')
#oid.after_login
def create_or_login(resp):
match = _steam_id_re.search(resp.identity_url)
g.user = User.get_or_create(match.group(1))
steamdata = get_steam_userinfo(g.user.steam_id)
g.user.nickname = steamdata['personaname']
db.session.commit()
session['user_id'] = g.user.id
flash('You are logged in as %s' % g.user.nickname)
return redirect(oid.get_next_url())
#app.before_request
def before_request():
g.user = None
if 'user_id' in session:
g.user = User.query.get(session['user_id'])
#app.route('/')
def homepage():
return render_template('mainpage.html')
#app.route('/logout')
def logout():
session.pop('user_id', None)
return redirect(oid.get_next_url())
if __name__ == '__main__':
app.run(debug=True)
You need to run a db.create_all() before running your app.
This will create all the tables described by your model in the database.
If you are new to flask you can follow the quickstart quide here

Categories

Resources