Skip to content

Commit

Permalink
Starting tests rewrite.
Browse files Browse the repository at this point in the history
  • Loading branch information
ColtonProvias committed Aug 17, 2016
1 parent 62c7706 commit 223d144
Show file tree
Hide file tree
Showing 14 changed files with 689 additions and 334 deletions.
199 changes: 86 additions & 113 deletions tests/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""
SQLAlchemy JSONAPI Test App.
This app implements the backend of a web forum. It's rather simple but
provides us with a more comprehensive example for testing.
Colton Provias <[email protected]>
MIT License
"""
Expand All @@ -9,13 +12,15 @@

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Boolean, Column, ForeignKey, Unicode, UnicodeText
from sqlalchemy import Boolean, Column, Enum, ForeignKey, Unicode, UnicodeText
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref, relationship, validates
from sqlalchemy_jsonapi import (INTERACTIVE_PERMISSIONS, Endpoint,
FlaskJSONAPI, Method, Permissions,
permission_test)
from sqlalchemy_utils import EmailType, PasswordType, Timestamp, UUIDType
from sqlalchemy_utils import (EmailType, IPAddressType, PasswordType,
Timestamp, URLType, UUIDType)

import enum

# ================================ APP CONFIG ================================

app = Flask(__name__)

Expand All @@ -26,149 +31,117 @@
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SQLALCHEMY_ECHO'] = False

#api = FlaskJSONAPI(app, db)

class User(Timestamp, db.Model):
"""Quick and dirty user model."""
# ================================== MODELS ==================================

#: If __jsonapi_type__ is not provided, it will use the class name instead.

class User(db.Model, Timestamp):
__tablename__ = 'users'

id = Column(UUIDType, default=uuid4, primary_key=True)
username = Column(Unicode(30), unique=True, nullable=False)
id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False)
email = Column(EmailType, nullable=False)
password = Column(
PasswordType(schemes=['bcrypt']),
nullable=False)
display_name = Column(Unicode(100), nullable=False)
password = Column(PasswordType(schemes=['bcrypt']), nullable=False)
is_admin = Column(Boolean, default=False)

@hybrid_property
def total_comments(self):
"""
Total number of comments.
Provides an example of a computed property.
"""
return self.comments.count()
last_ip_address = Column(IPAddressType)
website = Column(URLType)

@validates('email')
def validate_email(self, key, email):
"""Strong email validation."""
assert '@' in email, 'Not an email'
return email

@validates('username')
def validate_username(self, key, username):
"""
Check the length of the username.
Here's hoping nobody submits something in unicode that is 31 characters
long!!
"""
assert len(username) >= 4 and len(
username) <= 30, 'Must be 4 to 30 characters long.'
return username

@validates('password')
def validate_password(self, key, password):
"""Validate a password's length."""
assert len(password) >= 5, 'Password must be 5 characters or longer.'
return password

@jsonapi_access(Permissions.VIEW, 'password')
def view_password(self):
""" Never let the password be seen. """
return False

@jsonapi_access(Permissions.EDIT)
def prevent_edit(self):
""" Prevent editing for no reason. """
if request.view_args['api_type'] == 'blog-posts':
return True
return False

@jsonapi_access(Permissions.DELETE)
def allow_delete(self):
""" Just like a popular social media site, we won't delete users. """
return False


class BlogPost(Timestamp, db.Model):
"""Post model, as if this is a blog."""

class Forum(db.Model, Timestamp):
__tablename__ = 'forums'

id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False)
name = Column(Unicode(255), nullable=False)
can_public_read = Column(Boolean, default=True)
can_public_write = Column(Boolean, default=True)


class Thread(db.Model, Timestamp):
__tablename__ = 'threads'

id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False)
forum_id = Column(UUIDType, ForeignKey('forums.id'), nullable=False)
started_by_id = Column(UUIDType, ForeignKey('users.id'), nullable=False)
title = Column(Unicode(255), nullable=False)


class Post(db.Model, Timestamp):
__tablename__ = 'posts'

id = Column(UUIDType, default=uuid4, primary_key=True)
title = Column(Unicode(100), nullable=False)
slug = Column(Unicode(100))
id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False)
user_id = Column(UUIDType, ForeignKey('posts.id'), nullable=False)
content = Column(UnicodeText, nullable=False)
is_published = Column(Boolean, default=False)
author_id = Column(UUIDType, ForeignKey('users.id'))
is_removed = Column(Boolean, default=False)

author = relationship('User',
lazy='joined',
backref=backref('posts', lazy='dynamic'))

@validates('title')
def validate_title(self, key, title):
"""Keep titles from getting too long."""
assert len(title) >= 5 or len(
title) <= 100, 'Must be 5 to 100 characters long.'
return title
class ReportTypes(enum.Enum):
USER = 0
POST = 1

@jsonapi_access(Permissions.VIEW)
def allow_view(self):
""" Hide unpublished. """
return self.is_published

@jsonapi_access(INTERACTIVE_PERMISSIONS, 'logs')
def prevent_altering_of_logs(self):
return False
class Report(db.Model, Timestamp):
__tablename__ = 'reports'

id = Column(UUIDType, default=uuid4, primary_key=True, nullable=False)
report_type = Column(Enum(ReportTypes), nullable=False)
reporter_id = Column(UUIDType, ForeignKey('users.id'), nullable=False)
complaint = Column(UnicodeText, nullable=False)

class BlogComment(Timestamp, db.Model):
"""Comment for each Post."""
__mapper_args__ = {
'polymorphic_identity': 'employee',
'polymorphic_on': report_type,
'with_polymorphic': '*'
}

__tablename__ = 'comments'

id = Column(UUIDType, default=uuid4, primary_key=True)
post_id = Column(UUIDType, ForeignKey('posts.id'))
author_id = Column(UUIDType, ForeignKey('users.id'), nullable=False)
content = Column(UnicodeText, nullable=False)
class UserReport(db.Model):
__tablename__ = 'user_reports'

post = relationship('BlogPost',
lazy='joined',
backref=backref('comments', lazy='dynamic'))
author = relationship('User',
lazy='joined',
backref=backref('comments',
lazy='dynamic'))
id = Column(
UUIDType,
ForeignKey('reports.id'),
default=uuid4,
primary_key=True,
nullable=False)
user_id = Column(UUIDType, ForeignKey('users.id'), nullable=False)

__mapper_args__ = {'polymorphic_identity': ReportTypes.USER}


class Log(Timestamp, db.Model):
__tablename__ = 'logs'
id = Column(UUIDType, default=uuid4, primary_key=True)
post_id = Column(UUIDType, ForeignKey('posts.id'))
user_id = Column(UUIDType, ForeignKey('users.id'))
class PostReport(db.Model):
__tablename__ = 'post_reports'

id = Column(
UUIDType,
ForeignKey('reports.id'),
default=uuid4,
primary_key=True,
nullable=False)
post_id = Column(UUIDType, ForeignKey('posts.id'), nullable=False)

post = relationship('BlogPost',
lazy='joined',
backref=backref('logs', lazy='dynamic'))
user = relationship('User',
lazy='joined',
backref=backref('logs', lazy='dynamic'))
__mapper_args__ = {'polymorphic_identity': ReportTypes.POST}

@jsonapi_access(INTERACTIVE_PERMISSIONS)
def block_interactive(cls):
return False
# ============================== EVENT HANDLERS ==============================


api = FlaskJSONAPI(app, db)
@app.before_request
def handle_auth():
pass

# ============================== API OVERRIDES ==============================

@api.wrap_handler(['blog-posts'], [Method.GET], [Endpoint.COLLECTION])
def sample_override(next, *args, **kwargs):
return next(*args, **kwargs)
#@api.wrap_handler(['blog-posts'], [Method.GET], [Endpoint.COLLECTION])
#def sample_override(next, *args, **kwargs):
# return next(*args, **kwargs)

# ================================ APP RUNNER ================================

if __name__ == '__main__':
app.run()
81 changes: 14 additions & 67 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,24 @@

import json

import jsonschema
import pytest
from addict import Dict
from faker import Faker
from flask import Response
from flask.testing import FlaskClient
from sqlalchemy.orm import sessionmaker

from app import db as db_
from app import app, User, BlogPost, BlogComment, Log
from faker import Faker
from app import app

Session = sessionmaker()

fake = Faker()

with open('tests/jsonapi_schema.json', 'r') as f:
api_schema = json.load(f)


@pytest.yield_fixture(scope='session')
def flask_app():
Expand Down Expand Up @@ -57,7 +63,9 @@ def validate(self, status_code, error=None):
assert self.status_code == status_code
assert self.headers['Content-Type'] == 'application/vnd.api+json'
if status_code != 204:
self.json_data = json.loads(self.data.decode())
json_data = json.loads(self.data.decode())
jsonschema.validate(json_data, api_schema)
self.json_data = Dict(json_data)
if error:
assert self.status_code == error.status_code
assert self.json_data['errors'][0]['code'] == error.code
Expand All @@ -69,68 +77,7 @@ def validate(self, status_code, error=None):
@pytest.fixture
def client(flask_app):
"""Set up the testing client."""
with FlaskClient(flask_app,
use_cookies=True,
response_wrapper=TestingResponse) as c:
with FlaskClient(
flask_app, use_cookies=True,
response_wrapper=TestingResponse) as c:
return c


@pytest.fixture
def user(session):
new_user = User(email=fake.email(),
password=fake.sentence(),
username=fake.user_name())
session.add(new_user)
session.commit()
return new_user


@pytest.fixture
def post(user, session):
new_post = BlogPost(author=user,
title=fake.sentence(),
content=fake.paragraph(),
is_published=True)
session.add(new_post)
session.commit()
return new_post


@pytest.fixture
def unpublished_post(user, session):
new_post = BlogPost(author=user,
title=fake.sentence(),
content=fake.paragraph(),
is_published=False)
session.add(new_post)
session.commit()
return new_post


@pytest.fixture
def bunch_of_posts(user, session):
for x in range(30):
new_post = BlogPost(author=user,
title=fake.sentence(),
content=fake.paragraph(),
is_published=fake.boolean())
session.add(new_post)
new_post.comments.append(BlogComment(author=user,
content=fake.paragraph()))
session.commit()


@pytest.fixture
def comment(user, post, session):
new_comment = BlogComment(author=user, post=post, content=fake.paragraph())
session.add(new_comment)
session.commit()
return new_comment


@pytest.fixture
def log(user, post, session):
new_log = Log(user=user, post=post)
session.add(new_log)
session.commit()
return new_log
Loading

0 comments on commit 223d144

Please sign in to comment.