Skip to content

Commit

Permalink
Add hidden links
Browse files Browse the repository at this point in the history
  • Loading branch information
jacebrowning committed Jun 16, 2015
1 parent 83ac4de commit e0698b2
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 27 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ ifndef TRAVIS
endif

# Test settings
UNIT_TEST_COVERAGE := 0
INTEGRATION_TEST_COVERAGE := 0
COMBINED_TEST_COVERAGE := 0
UNIT_TEST_COVERAGE := 53
INTEGRATION_TEST_COVERAGE := 75
COMBINED_TEST_COVERAGE := 90

# System paths
PLATFORM := $(shell python -c 'import sys; print(sys.platform)')
Expand Down
3 changes: 2 additions & 1 deletion memegen/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def create_app(config):

def register_services(app):
_exceptions = services.Exceptions(
missing=exceptions.NotFound,
not_found=exceptions.NotFound,
bad_code=exceptions.NotFound,
)

_template_store = stores.template.TemplateStore()
Expand Down
3 changes: 3 additions & 0 deletions memegen/domain/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
class Image:
"""Meme image generated from a template."""

# TODO: support more image types
KINDS = ('jpg',) # 'png', 'gif')

@classmethod
def from_template(cls, template, text, kind):
make_meme(text.top, text.bottom, template.path)
Expand Down
18 changes: 15 additions & 3 deletions memegen/routes/image.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
from flask import Blueprint, current_app as app, send_file
from flask import Blueprint, current_app as app, redirect, url_for, send_file

from .. import domain

blueprint = Blueprint('image', __name__, url_prefix="/")


@blueprint.route("<key>/<top>/<bottom>.<kind>")
def get(key, top, bottom, kind):
def get_visible(key, top, bottom, kind):
template = domain.Template(key)
text = domain.Text(top, bottom)
_path = app.image_service.create_image(template, text, kind)
return send_file(_path, mimetype='image/png')
return send_file(_path, mimetype='image/jpeg')


@blueprint.route("<code>.<kind>")
def get_hidden(code, kind):
key, top, bottom = app.link_service.decode(code)
# TODO: maybe this shouldn't redirect
# url = url_for('.get_visible', key=key, top=top, bottom=bottom, kind=kind)
# return redirect(url)
template = domain.Template(key)
text = domain.Text(top, bottom)
_path = app.image_service.create_image(template, text, kind)
return send_file(_path, mimetype='image/jpeg')
20 changes: 17 additions & 3 deletions memegen/routes/link.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
from flask import Blueprint
from collections import OrderedDict

from flask import Blueprint, current_app as app, url_for

from ..domain import Image


blueprint = Blueprint('link', __name__, url_prefix="/")


@blueprint.route("<key>/<top>/<bottom>")
def get(key, top, bottom):
return {}
def get(**kwargs):
data = OrderedDict()
data['visible'] = OrderedDict()
data['hidden'] = OrderedDict()
for kind in Image.KINDS:
url = url_for('image.get_visible', kind=kind, _external=True, **kwargs)
data['visible'][kind] = url
code = app.link_service.encode(**kwargs)
url = url_for('image.get_hidden', kind=kind, _external=True, code=code)
data['hidden'][kind] = url
return data
18 changes: 1 addition & 17 deletions memegen/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,2 @@
from abc import ABCMeta


class Exceptions:

def __init__(self, missing=KeyError):
self.missing = missing


class Service(metaclass=ABCMeta):

"""Base class for domain services."""

def __init__(self, exceptions=None):
self.exceptions = exceptions or Exceptions()


from ._base import Exceptions, Service
from . import image, link, template
16 changes: 16 additions & 0 deletions memegen/services/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from abc import ABCMeta


class Exceptions:

def __init__(self, not_found=KeyError, bad_code=ValueError):
self.not_found = not_found
self.bad_code = bad_code


class Service(metaclass=ABCMeta):

"""Base class for domain services."""

def __init__(self, exceptions=None):
self.exceptions = exceptions or Exceptions()
18 changes: 18 additions & 0 deletions memegen/services/link.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import base64

from . import Service


Expand All @@ -6,3 +8,19 @@ class LinkService(Service):
def __init__(self, template_store, **kwargs):
super().__init__(**kwargs)
self.template_store = template_store

def encode(self, key, top, bottom):
slug = '\t'.join((key, top, bottom))
while len(slug) % 3:
slug += ' '
code = base64.urlsafe_b64encode(slug.encode('utf-8'))
return code

def decode(self, code):
try:
slug = base64.urlsafe_b64decode(code).decode('utf-8')
except ValueError:
raise self.exceptions.bad_code
else:
key, top, bottom = slug.strip().split('\t')
return key, top, bottom
10 changes: 10 additions & 0 deletions memegen/test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from unittest.mock import Mock

import pytest

from memegen import services


@pytest.fixture
def link_service():
return services.link.LinkService(template_store=Mock())
17 changes: 17 additions & 0 deletions memegen/test/test_services_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest


class TestLinkService:

def test_decode_encoded_parts(self, link_service):
code = link_service.encode("a", "b", "c")
parts = link_service.decode(code)
assert ("a", "b", "c") == parts

def test_decode_invalid_code(self, link_service):
with pytest.raises(ValueError):
link_service.decode("bad_code")

def test_decode_empty_code(self, link_service):
with pytest.raises(ValueError):
link_service.decode(b"")
18 changes: 18 additions & 0 deletions memegen/test/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest

from memegen.settings import Config, get_config


class TestGetConfig:

def test_get_valid(self):
config = get_config('prod')
assert issubclass(config, Config)

def test_get_none(self):
with pytest.raises(AssertionError):
get_config('')

def test_get_unknown(self):
with pytest.raises(AssertionError):
get_config('not_a_valid_config')
29 changes: 29 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json
import logging

import pytest

from memegen.app import create_app
from memegen.settings import get_config


def load(response):
"""Convert a response's binary data (JSON) to a dictionary."""
text = response.data.decode('utf-8')
if text:
data = json.loads(text)
else:
data = None
logging.debug("response: %r", data)
return data



@pytest.fixture
def app():
return create_app(get_config('test'))


@pytest.fixture
def client(app):
return app.test_client()
41 changes: 41 additions & 0 deletions tests/test_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from .conftest import load


class TestMeme:

def test_get_jpg(self, client):
response = client.get("/iw/hello/world.jpg")
assert response.status_code == 200
assert response.mimetype == 'image/jpeg'

# TODO: add more image types

# def test_get_jpeg(self, client):
# response = client.get("/iw/hello/world.jpeg")
# assert response.status_code == 200
# assert response.mimetype == 'image/jpeg'

# def test_get_png(self, client):
# response = client.get("/iw/hello/world.png")
# assert response.status_code == 200
# assert response.mimetype == 'image/png'

# def test_get_gif(self, client):
# response = client.get("/iw/hello/world.gif")
# assert response.status_code == 200
# assert response.mimetype == 'image/gif'


class TestLink:

def test_get_links(self, client):
response = client.get("/iw/hello/world")
assert response.status_code == 200
assert load(response) == dict(
visible=dict(
jpg="http://localhost/iw/hello/world.jpg",
),
hidden=dict(
jpg="http://localhost/aXcJaGVsbG8Jd29ybGQg.jpg",
),
)

0 comments on commit e0698b2

Please sign in to comment.