Bring code up-to-date
This commit is contained in:
parent
b939ad415d
commit
97512598f5
10
main.py
10
main.py
|
@ -1,20 +1,18 @@
|
|||
"""
|
||||
Main Flask-based app for Slinky
|
||||
"""
|
||||
|
||||
from flask import Flask, Response, render_template
|
||||
from flask_bootstrap import Bootstrap # type: ignore[import]
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from slinky.web import protect, slinky_webapp
|
||||
|
||||
app = Flask(__name__)
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) # type: ignore[assignment]
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1)
|
||||
app.register_blueprint(slinky_webapp)
|
||||
|
||||
Bootstrap(app)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@app.route("/")
|
||||
@protect
|
||||
def index() -> Response:
|
||||
"""
|
||||
|
@ -23,4 +21,4 @@ def index() -> Response:
|
|||
Returns:
|
||||
str: string of page content
|
||||
"""
|
||||
return Response(render_template('index.html'), 200)
|
||||
return Response(render_template("index.html"), 200)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
flask
|
||||
flask_bootstrap
|
||||
flask_wtf
|
||||
psycopg2-binary
|
||||
pyyaml
|
||||
|
|
|
@ -6,9 +6,6 @@ import random
|
|||
import string
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import sqlalchemy # type: ignore[import]
|
||||
|
||||
from slinky import db
|
||||
|
||||
|
@ -38,7 +35,7 @@ def random_string(length: int = 4) -> str:
|
|||
"""
|
||||
allowed_chars: str = string.ascii_letters + string.digits
|
||||
|
||||
return ''.join(random.SystemRandom().choice(allowed_chars) for _ in range(length))
|
||||
return "".join(random.SystemRandom().choice(allowed_chars) for _ in range(length))
|
||||
|
||||
|
||||
class Slinky:
|
||||
|
@ -52,8 +49,8 @@ class Slinky:
|
|||
|
||||
def add( # pylint: disable=too-many-arguments
|
||||
self,
|
||||
shortcode: str = '',
|
||||
url: str = '',
|
||||
shortcode: str = "",
|
||||
url: str = "",
|
||||
length: int = 4,
|
||||
fixed_views: int = -1,
|
||||
expiry: datetime = datetime.max,
|
||||
|
@ -78,7 +75,7 @@ class Slinky:
|
|||
shortcode = random_string(length=length)
|
||||
|
||||
if self.get_by_shortcode(shortcode).url:
|
||||
raise ValueError(f'Shortcode {shortcode} already exists')
|
||||
raise ValueError(f"Shortcode {shortcode} already exists")
|
||||
|
||||
dbentry = db.ShortURL(
|
||||
shortcode=shortcode,
|
||||
|
@ -114,7 +111,7 @@ class Slinky:
|
|||
)
|
||||
self.session.close()
|
||||
return ret_sc
|
||||
return Shortcode(0, '', '', 0, '1970-01-01 00:00:00.000000')
|
||||
return Shortcode(0, "", "", 0, "1970-01-01 00:00:00.000000")
|
||||
|
||||
def remove_view(self, sc_id: int) -> None:
|
||||
"""
|
||||
|
|
11
slinky/db.py
11
slinky/db.py
|
@ -1,21 +1,22 @@
|
|||
"""
|
||||
DB component
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import Column, Integer, String, create_engine # type: ignore[import]
|
||||
from sqlalchemy.ext.declarative import declarative_base # type: ignore[import]
|
||||
from sqlalchemy.orm import Session, sessionmaker # type: ignore[import]
|
||||
from sqlalchemy import Column, Integer, String, create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class ShortURL(Base): # type: ignore[misc, valid-type] # pylint: disable=too-few-public-methods
|
||||
class ShortURL(Base): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Class to describe the DB schema for ShortURLs
|
||||
"""
|
||||
|
||||
__tablename__ = 'shorturl'
|
||||
__tablename__ = "shorturl"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
shortcode = Column(String(128), unique=True, nullable=False)
|
||||
|
|
141
slinky/web.py
141
slinky/web.py
|
@ -10,71 +10,70 @@ from typing import Any, Callable
|
|||
|
||||
import yaml
|
||||
from flask import Blueprint, Response, render_template, request
|
||||
from flask_wtf import FlaskForm # type: ignore[import]
|
||||
from wtforms import HiddenField # type: ignore[import]
|
||||
from wtforms import DateTimeLocalField, IntegerField, StringField
|
||||
from wtforms.validators import DataRequired, Length # type: ignore[import]
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import DateTimeLocalField, HiddenField, IntegerField, StringField
|
||||
from wtforms.validators import DataRequired, Length
|
||||
|
||||
from slinky import Slinky, random_string
|
||||
|
||||
slinky_webapp = Blueprint('webapp', __name__, template_folder='templates')
|
||||
slinky_webapp = Blueprint("webapp", __name__, template_folder="templates")
|
||||
|
||||
with open('config.yaml', encoding='utf-8-sig') as conffile:
|
||||
with open("config.yaml", encoding="utf-8-sig") as conffile:
|
||||
cfg = yaml.safe_load(conffile)
|
||||
|
||||
|
||||
class DelForm(FlaskForm): # type: ignore[misc]
|
||||
class DelForm(FlaskForm):
|
||||
"""
|
||||
Delete form definition
|
||||
"""
|
||||
|
||||
delete = HiddenField('delete')
|
||||
delete = HiddenField("delete")
|
||||
|
||||
|
||||
class AddForm(FlaskForm): # type: ignore[misc]
|
||||
class AddForm(FlaskForm):
|
||||
"""
|
||||
Add form definition
|
||||
"""
|
||||
|
||||
shortcode = StringField(
|
||||
'Shortcode',
|
||||
"Shortcode",
|
||||
validators=[DataRequired(), Length(1, 2048)],
|
||||
render_kw={
|
||||
'size': 64,
|
||||
'maxlength': 2048,
|
||||
"size": 64,
|
||||
"maxlength": 2048,
|
||||
},
|
||||
)
|
||||
url = StringField(
|
||||
'URL',
|
||||
"URL",
|
||||
validators=[DataRequired(), Length(1, 2048)],
|
||||
render_kw={
|
||||
'size': 64,
|
||||
'maxlength': 2048,
|
||||
'placeholder': 'https://www.example.com',
|
||||
"size": 64,
|
||||
"maxlength": 2048,
|
||||
"placeholder": "https://www.example.com",
|
||||
},
|
||||
)
|
||||
fixed_views = IntegerField(
|
||||
'Fixed number of views',
|
||||
"Fixed number of views",
|
||||
validators=[DataRequired()],
|
||||
render_kw={
|
||||
'size': 3,
|
||||
'value': -1,
|
||||
"size": 3,
|
||||
"value": -1,
|
||||
},
|
||||
)
|
||||
length = IntegerField(
|
||||
'Shortcode length',
|
||||
"Shortcode length",
|
||||
validators=[DataRequired()],
|
||||
render_kw={
|
||||
'size': 3,
|
||||
'value': 4,
|
||||
"size": 3,
|
||||
"value": 4,
|
||||
},
|
||||
)
|
||||
expiry = DateTimeLocalField(
|
||||
'Expiry',
|
||||
format='%Y-%m-%dT%H:%M',
|
||||
"Expiry",
|
||||
format="%Y-%m-%dT%H:%M",
|
||||
render_kw={
|
||||
'size': 8,
|
||||
'maxlength': 10,
|
||||
"size": 8,
|
||||
"maxlength": 10,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -92,18 +91,26 @@ def protect(func: Callable[..., Response]) -> Callable[..., Response]:
|
|||
|
||||
@wraps(func)
|
||||
def check_ip(*args: Any, **kwargs: Any) -> Response:
|
||||
remote_addr = request.remote_addr
|
||||
|
||||
if "x-forwarded-for" in request.headers:
|
||||
remote_addr = request.headers["x-forwarded-for"]
|
||||
|
||||
if "x-real-ip" in request.headers:
|
||||
remote_addr = request.headers["x-real-ip"]
|
||||
|
||||
if (
|
||||
os.environ.get('FLASK_ENV', '') != 'development'
|
||||
and request.headers['X-Forwarded-For'] not in cfg['allowed_ips']
|
||||
os.environ.get("FLASK_ENV", "") != "development"
|
||||
and remote_addr not in cfg["allowed_ips"]
|
||||
):
|
||||
logging.warning('Protected URL access attempt from %s', request.remote_addr)
|
||||
return Response('Not found', 404)
|
||||
logging.warning("Protected URL access attempt from %s", remote_addr)
|
||||
return Response("Not found", 404)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return check_ip
|
||||
|
||||
|
||||
@slinky_webapp.route('/<path:path>', strict_slashes=False)
|
||||
@slinky_webapp.route("/<path:path>", strict_slashes=False)
|
||||
def try_path_as_shortcode(path: str) -> Response:
|
||||
"""
|
||||
Try the initial path as a shortcode, redirect if found
|
||||
|
@ -112,27 +119,27 @@ def try_path_as_shortcode(path: str) -> Response:
|
|||
Response: redirect if found, otherwise 404
|
||||
"""
|
||||
should_redirect = True
|
||||
slinky = Slinky(cfg['db'])
|
||||
slinky = Slinky(cfg["db"])
|
||||
shortcode = slinky.get_by_shortcode(path)
|
||||
if shortcode.url:
|
||||
if shortcode.fixed_views == 0:
|
||||
logging.warning('Shortcode out of views')
|
||||
logging.warning("Shortcode out of views")
|
||||
should_redirect = False
|
||||
elif shortcode.fixed_views > 0:
|
||||
slinky.remove_view(shortcode.id)
|
||||
if datetime.fromisoformat(shortcode.expiry) < datetime.now():
|
||||
logging.warning('Shortcode expired')
|
||||
logging.warning("Shortcode expired")
|
||||
should_redirect = False
|
||||
|
||||
if should_redirect:
|
||||
return Response(
|
||||
'Redirecting...', status=302, headers={'location': shortcode.url}
|
||||
"Redirecting...", status=302, headers={"location": shortcode.url}
|
||||
)
|
||||
|
||||
return Response('Not found', 404)
|
||||
return Response("Not found", 404)
|
||||
|
||||
|
||||
@slinky_webapp.route('/_/add', methods=['GET', 'POST'])
|
||||
@slinky_webapp.route("/_/add", methods=["GET", "POST"])
|
||||
@protect
|
||||
def add() -> Response:
|
||||
"""
|
||||
|
@ -141,27 +148,27 @@ def add() -> Response:
|
|||
Returns:
|
||||
Response: HTTP response
|
||||
"""
|
||||
slinky = Slinky(cfg['db'])
|
||||
slinky = Slinky(cfg["db"])
|
||||
|
||||
for attempts in range(50):
|
||||
shortcode = random_string()
|
||||
if slinky.get_by_shortcode(shortcode).url:
|
||||
logging.warning(
|
||||
'Shortcode already exists. Retrying (%s/50).',
|
||||
"Shortcode already exists. Retrying (%s/50).",
|
||||
attempts,
|
||||
)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return Response(
|
||||
render_template('error.html', msg='Could not create a unique shortcode'),
|
||||
render_template("error.html", msg="Could not create a unique shortcode"),
|
||||
500,
|
||||
)
|
||||
|
||||
url = ''
|
||||
final_url = ''
|
||||
url = ""
|
||||
final_url = ""
|
||||
|
||||
form = AddForm(meta={'csrf': False})
|
||||
form = AddForm(meta={"csrf": False})
|
||||
|
||||
if form.is_submitted():
|
||||
shortcode = form.shortcode.data.strip()
|
||||
|
@ -170,25 +177,25 @@ def add() -> Response:
|
|||
fixed_views = form.fixed_views.data
|
||||
expiry = form.expiry.data or datetime.max
|
||||
|
||||
if url:
|
||||
try:
|
||||
shortcode = slinky.add(
|
||||
shortcode=shortcode,
|
||||
url=url,
|
||||
length=length,
|
||||
fixed_views=fixed_views,
|
||||
expiry=expiry,
|
||||
)
|
||||
except ValueError as error:
|
||||
logging.warning(error)
|
||||
return Response(render_template('error.html', msg=error), 400)
|
||||
if url:
|
||||
try:
|
||||
shortcode = slinky.add(
|
||||
shortcode=shortcode,
|
||||
url=url,
|
||||
length=length,
|
||||
fixed_views=fixed_views,
|
||||
expiry=expiry,
|
||||
)
|
||||
except ValueError as error:
|
||||
logging.warning(error)
|
||||
return Response(render_template("error.html", msg=error), 400)
|
||||
|
||||
if form.is_submitted():
|
||||
final_url = f'{request.host_url}/{shortcode}'
|
||||
if form.is_submitted():
|
||||
final_url = f"{request.host_url}/{shortcode}"
|
||||
|
||||
return Response(
|
||||
render_template(
|
||||
'add.html',
|
||||
"add.html",
|
||||
form=form,
|
||||
shortcode=shortcode,
|
||||
final_url=final_url,
|
||||
|
@ -197,7 +204,7 @@ def add() -> Response:
|
|||
)
|
||||
|
||||
|
||||
@slinky_webapp.route('/_/list', methods=['GET', 'POST'])
|
||||
@slinky_webapp.route("/_/list", methods=["GET", "POST"])
|
||||
@protect
|
||||
def lister() -> Response:
|
||||
"""
|
||||
|
@ -206,19 +213,19 @@ def lister() -> Response:
|
|||
Returns:
|
||||
Response: HTTP response
|
||||
"""
|
||||
form = DelForm(meta={'csrf': False})
|
||||
slinky = Slinky(cfg['db'])
|
||||
form = DelForm(meta={"csrf": False})
|
||||
slinky = Slinky(cfg["db"])
|
||||
|
||||
if form.is_submitted():
|
||||
slinky.delete_by_shortcode(form.delete.data.strip())
|
||||
|
||||
return Response(
|
||||
render_template('list.html', form=form, shortcodes=slinky.get_all()),
|
||||
render_template("list.html", form=form, shortcodes=slinky.get_all()),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@slinky_webapp.route('/_/edit/<int:id>', methods=['GET', 'POST'])
|
||||
@slinky_webapp.route("/_/edit/<int:id>", methods=["GET", "POST"])
|
||||
@protect
|
||||
def edit(id: int) -> Response: # pylint: disable=invalid-name,redefined-builtin
|
||||
"""
|
||||
|
@ -227,15 +234,15 @@ def edit(id: int) -> Response: # pylint: disable=invalid-name,redefined-builtin
|
|||
Returns:
|
||||
Response: HTTP response
|
||||
"""
|
||||
form = DelForm(meta={'csrf': False})
|
||||
slinky = Slinky(cfg['db'])
|
||||
form = DelForm(meta={"csrf": False})
|
||||
slinky = Slinky(cfg["db"])
|
||||
|
||||
logging.debug('Editing: %d', id)
|
||||
logging.debug("Editing: %d", id)
|
||||
|
||||
if form.is_submitted():
|
||||
slinky.delete_by_shortcode(form.delete.data.strip())
|
||||
|
||||
return Response(
|
||||
render_template('edit.html', form=form, shortcodes=slinky.get_all()),
|
||||
render_template("edit.html", form=form, shortcodes=slinky.get_all()),
|
||||
200,
|
||||
)
|
||||
|
|
|
@ -1,98 +1,97 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
|
||||
<title>Slinky</title>
|
||||
<title>Slinky</title>
|
||||
|
||||
<link rel="canonical" href="https://getbootstrap.com/docs/5.0/examples/navbar-static/">
|
||||
<link rel="canonical" href="https://getbootstrap.com/docs/5.0/examples/navbar-static/">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{{url_for('bootstrap.static', filename='datepicker.css')}}">
|
||||
<meta name="theme-color" content="#7952b3">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<meta name="theme-color" content="#7952b3">
|
||||
|
||||
<style>
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.badge a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.badge.text-dark a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
<style>
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
[role=status] {
|
||||
display: none;
|
||||
}
|
||||
.badge a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tooltip>.tooltip-inner {
|
||||
max-width: 60em !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
.badge.text-dark a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #f48024;
|
||||
color: #1d1d1e;
|
||||
border-radius: 2px;
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
padding: 0 1px 0 1px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.checkboxlist ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
[role=status] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<!-- <link href="navbar-top.css" rel="stylesheet"> -->
|
||||
<script>
|
||||
// <![CDATA[
|
||||
function waiting() {
|
||||
document.querySelectorAll('[role="status"]')[0].style.display = "inline-block";
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
</head>
|
||||
.tooltip>.tooltip-inner {
|
||||
max-width: 60em !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Slinky</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
|
||||
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/_/add">Add</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/_/list">List</a>
|
||||
</li>
|
||||
</ul>
|
||||
.highlight {
|
||||
background-color: #f48024;
|
||||
color: #1d1d1e;
|
||||
border-radius: 2px;
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
padding: 0 1px 0 1px;
|
||||
}
|
||||
|
||||
.checkboxlist ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<!-- <link href="navbar-top.css" rel="stylesheet"> -->
|
||||
<script>
|
||||
// <![CDATA[
|
||||
function waiting() {
|
||||
document.querySelectorAll('[role="status"]')[0].style.display = "inline-block";
|
||||
}
|
||||
// ]]>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Slinky</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
|
||||
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/_/add">Add</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/_/list">List</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf" crossorigin="anonymous">
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
<script>
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
|
|
|
@ -5,7 +5,7 @@ Test Slinky web interface
|
|||
from unittest import TestCase, mock
|
||||
|
||||
from flask import Flask
|
||||
from flask_bootstrap import Bootstrap # type: ignore[import]
|
||||
|
||||
from slinky.web import slinky_webapp
|
||||
|
||||
|
||||
|
@ -15,45 +15,43 @@ class TestWeb(TestCase):
|
|||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.app = Flask(__name__, template_folder='../templates')
|
||||
self.app = Flask(__name__, template_folder="../templates")
|
||||
self.app.register_blueprint(slinky_webapp)
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
self.client = self.app.test_client()
|
||||
|
||||
Bootstrap(self.app)
|
||||
|
||||
mock.patch.dict('slinky.web.cfg', {'db': 'sqlite:///tests/test.db'}).start()
|
||||
mock.patch.dict("slinky.web.cfg", {"db": "sqlite:///tests/test.db"}).start()
|
||||
|
||||
def test_simple_redirect(self) -> None:
|
||||
"""
|
||||
Ensure simple redirect works
|
||||
"""
|
||||
response = self.client.get('/egie')
|
||||
response = self.client.get("/egie")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.location, 'https://example.com')
|
||||
self.assertEqual(response.location, "https://example.com")
|
||||
|
||||
def test_fixed_views(self) -> None:
|
||||
"""
|
||||
Ensure depleted fixed views returns a 404
|
||||
"""
|
||||
response = self.client.get('/egig')
|
||||
response = self.client.get("/egig")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_expiry(self) -> None:
|
||||
"""
|
||||
Ensure expired redirect returns a 404
|
||||
"""
|
||||
response = self.client.get('/egif')
|
||||
response = self.client.get("/egif")
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_no_unique_shortcode(self) -> None:
|
||||
"""
|
||||
Ensure non-unique shortcode generation returns a 500 error
|
||||
"""
|
||||
with mock.patch('slinky.web.random_string', return_value='egie'):
|
||||
with mock.patch("slinky.web.random_string", return_value="egie"):
|
||||
response = self.client.get(
|
||||
'/_/add', headers={'x-forwarded-for': '127.0.0.1'}
|
||||
"/_/add", headers={"x-forwarded-for": "127.0.0.1"}
|
||||
)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
|
||||
|
@ -61,9 +59,9 @@ class TestWeb(TestCase):
|
|||
"""
|
||||
Test the condition where the random_string() returns an existing shortcode
|
||||
"""
|
||||
with mock.patch('slinky.web.random_string', side_effect=['egie', 'egiz']):
|
||||
with mock.patch("slinky.web.random_string", side_effect=["egie", "egiz"]):
|
||||
response = self.client.get(
|
||||
'/_/add',
|
||||
headers={'x-forwarded-for': '127.0.0.1'},
|
||||
"/_/add",
|
||||
headers={"x-forwarded-for": "127.0.0.1"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
Loading…
Reference in a new issue