Writing “unit” tests

The example below uses the module testing.postgresql to run each test case in an isolated postgres server instance all you need is the postgres binaries available in the host machine.

import unittest
import testing.postgresql
from chemist import set_default_uri
from models import User


class UserModelTestCase(unittest.TestCase):
    def setUp(self):
        self.postgresql = testing.postgresql.Postgresql()
        set_default_uri(self.postgresql.url())

    def tearDown(self):
        self.postgresql.stop()

    def test_authentication(self):
        # Given a user with a hardcoded password
        foobar = User.create('foo@bar.com', '123insecure')

        # When I match the password
        matched = foobar.match_password('123insecure')

        # Then it should have matched
        assert matched, f'user {foobar} did not match password 123insecure'


    def test_change_password(self):
        # Given a user with a hardcoded password
        foobar = User.create('foo@bar.com', '123insecure')

        # When I change the password
        changed = foobar.change_password('123insecure', 'newPassword')

        # Then it should have succeeded
        assert matched, f'failed to change password for {foobar}'

        # And should authenticate with the new password
        assert foobar.match_password('newPassword'), (
            f'user {foobar} did not match password newPassword')
import bcrypt
from chemist import (
    Model, db, metadata
    set_default_uri,
)

engine = set_default_uri('sqlite:///example.db')

CREDIT_CARD_ENCRYPTION_KEY = b'\xb3\x0f\xcc9\xc3\xb1k#\x95j4\xb3\x1f\x08\x98\xd7~6\xff\xceb\xdc\x17vW\xd7\x90\xcf\x82\x9d\xb7j'

class User(Model):
    table = db.Table(
        'auth_user',
        metadata,
        db.Column('id', db.Integer, primary_key=True),
        db.Column('email', db.String(100), nullable=False, unique=True),
        db.Column('password', db.String(100), nullable=False, unique=True),
        db.Column('created_at', db.DateTime, default=datetime.now),
        db.Column('updated_at', db.DateTime, default=datetime.now),
        db.Column('credit_card', db.String(16)),
    )
    encryption = {
        # transparently encrypt data data in "credit_card" field before storing on DB
        # also transparently decrypt after retrieving data
        'credit_card': CREDIT_CARD_ENCRYPTION_KEY,
    }

    @classmethod
    def create(cls, email, password, **kw):
        email = email.lower()
        password = cls.secretify_password(password)
        return super(User, cls).create(email=email, password=password, **kw)

    def to_dict(self):
        # prevent password and credit-card to be returned in HTTP responses that serialize model data
        data = self.serialize()
        data.pop('password', None)
        data.pop('credit_card', None)
        return data

    @classmethod
    def secretify_password(cls, plain):
        return bcrypt.hashpw(plain, bcrypt.gensalt(12))

    def match_password(self, plain):
        return self.password == bcrypt.hashpw(plain, self.password)

    def change_password(self, old_password, new_password):
        right_password = self.match_password(old_password)
        if right_password:
            secret = self.secretify_password(new_password)
            self.set(password=secret)
            self.save()
            return True

        return False