style: drop redundant comments and apply formatting
Frontend now uses double quotes and semicolons. No behaviour change.
This commit is contained in:
@@ -21,8 +21,6 @@ def create_app() -> Flask:
|
||||
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
# Werkzeug buffers and parses the whole body before our validation runs, so unauthenticated
|
||||
# callers could exhaust memory; 1 MiB is far above the largest legitimate payload (10,000-char note).
|
||||
app.config["MAX_CONTENT_LENGTH"] = 1024 * 1024
|
||||
db.init_app(app)
|
||||
auth.init_app(app)
|
||||
@@ -35,8 +33,6 @@ def create_app() -> Flask:
|
||||
|
||||
@app.before_request
|
||||
def require_json_for_mutations():
|
||||
# CSRF defense (ADR-0002): cross-site requests can only send this content type after a
|
||||
# CORS preflight, and this API never grants CORS.
|
||||
if request.method in MUTATING_METHODS and not request.is_json:
|
||||
raise ApiError(
|
||||
400, "Request body must be JSON with Content-Type: application/json"
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ INVALID_LOGIN = "Invalid username or password"
|
||||
|
||||
def init_app(app: Flask) -> None:
|
||||
app.extensions["password_pepper"] = load_pepper(Path(app.root_path) / ".pepper")
|
||||
_dummy_hash() # pay the one-time cost now so the first unknown-user login isn't measurably slower
|
||||
_dummy_hash()
|
||||
app.register_blueprint(bp)
|
||||
|
||||
|
||||
|
||||
+1
-3
@@ -13,7 +13,7 @@ bp = Blueprint("books", __name__, url_prefix="/api")
|
||||
|
||||
MAX_TEXT = 500
|
||||
MAX_PAGES = 100_000
|
||||
MAX_GENRE_ID = 32_767 # SMALLINT
|
||||
MAX_GENRE_ID = 32_767
|
||||
GENRE_ID_PATTERN = re.compile(r"[0-9]{1,5}")
|
||||
REQUIRED_FIELDS = ("title", "author", "genre_id", "total_pages")
|
||||
FIELDS = set(REQUIRED_FIELDS) | {"current_page"}
|
||||
@@ -56,7 +56,6 @@ def to_json(row: dict) -> dict:
|
||||
|
||||
|
||||
def escape_like(term: str) -> str:
|
||||
# Backslash is Postgres's default LIKE escape character.
|
||||
return term.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
||||
|
||||
|
||||
@@ -147,7 +146,6 @@ def create_book():
|
||||
return jsonify(to_json(owned_book(row["id"]))), 201
|
||||
|
||||
|
||||
# int(max=...) keeps ids within Postgres INT range: larger values 404 instead of erroring in SQL.
|
||||
@bp.get("/books/<int(max=2147483647):book_id>")
|
||||
@login_required
|
||||
def get_book(book_id: int):
|
||||
|
||||
+1
-2
@@ -44,7 +44,6 @@ def _connection():
|
||||
|
||||
|
||||
def _finish_transaction(response):
|
||||
# Commit only successful responses so a 4xx/5xx never leaves partial writes behind.
|
||||
conn = g.get("db")
|
||||
if conn is not None:
|
||||
if response.status_code < 400:
|
||||
@@ -57,5 +56,5 @@ def _finish_transaction(response):
|
||||
def _release_connection(_exc):
|
||||
conn = g.pop("db", None)
|
||||
if conn is not None:
|
||||
conn.rollback() # no-op after commit; discards work if after_request never ran
|
||||
conn.rollback()
|
||||
current_app.extensions["db_pool"].putconn(conn)
|
||||
|
||||
@@ -19,8 +19,6 @@ CREATE TABLE IF NOT EXISTS genres (
|
||||
name TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
-- WHERE NOT EXISTS instead of ON CONFLICT: ON CONFLICT consumes a sequence value per row
|
||||
-- on every startup, which would eventually overflow SMALLSERIAL.
|
||||
INSERT INTO genres (name)
|
||||
SELECT seed.name
|
||||
FROM (VALUES ('Fiction'), ('Non-Fiction'), ('Mystery'), ('Thriller'), ('Science Fiction'),
|
||||
|
||||
@@ -13,7 +13,7 @@ TEST_DATABASE = "books_test"
|
||||
|
||||
def _ensure_test_database() -> None:
|
||||
conn = psycopg2.connect(ADMIN_URL)
|
||||
conn.autocommit = True # CREATE DATABASE cannot run inside a transaction
|
||||
conn.autocommit = True
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -28,6 +28,5 @@ def _ensure_test_database() -> None:
|
||||
|
||||
|
||||
_ensure_test_database()
|
||||
# Assigned, never defaulted: tests TRUNCATE tables, so they must not inherit a real DATABASE_URL.
|
||||
os.environ["DATABASE_URL"] = ADMIN_URL.rsplit("/", 1)[0] + "/" + TEST_DATABASE
|
||||
os.environ["PASSWORD_PEPPER"] = "test-pepper-0123456789abcdef0123456789abcdef"
|
||||
|
||||
@@ -5,7 +5,7 @@ import psycopg2
|
||||
|
||||
from app import app
|
||||
|
||||
HTTPS = "https://localhost" # the session cookie is Secure
|
||||
HTTPS = "https://localhost"
|
||||
|
||||
|
||||
def db_execute(sql: str, params: tuple = ()) -> list[tuple]:
|
||||
|
||||
@@ -70,7 +70,7 @@ class AuthTests(ApiTestCase):
|
||||
|
||||
def test_login_is_case_insensitive_on_username(self):
|
||||
self.register("Alice")
|
||||
self.client = app.test_client() # fresh cookie jar: signed out
|
||||
self.client = app.test_client()
|
||||
response = self.call(
|
||||
"POST",
|
||||
"/api/auth/login",
|
||||
@@ -116,9 +116,7 @@ class AuthTests(ApiTestCase):
|
||||
self.register()
|
||||
token = self.session_token()
|
||||
self.assertEqual(self.call("POST", "/api/auth/logout").status_code, 204)
|
||||
self.client.set_cookie(
|
||||
"sid", token, domain="localhost", path="/api"
|
||||
) # replay the stolen cookie
|
||||
self.client.set_cookie("sid", token, domain="localhost", path="/api")
|
||||
self.assertEqual(self.call("GET", "/api/auth/me").status_code, 401)
|
||||
|
||||
def test_idle_session_expires(self):
|
||||
|
||||
@@ -17,7 +17,7 @@ class BookTests(ApiTestCase):
|
||||
(book["current_page"], book["total_pages"], book["status"]),
|
||||
(0, 412, "not_started"),
|
||||
)
|
||||
self.assertIn("T", book["created_at"]) # ISO 8601
|
||||
self.assertIn("T", book["created_at"])
|
||||
|
||||
def test_create_trims_text(self):
|
||||
book = self.create_book(title=" Dune ")
|
||||
|
||||
@@ -24,12 +24,10 @@ class SearchTests(ApiTestCase):
|
||||
self.assertEqual(self.titles("q=tolkien"), ["The Hobbit"])
|
||||
|
||||
def test_wildcards_match_literally(self):
|
||||
self.assertEqual(self.titles("q=100%25"), ["100% Pure"]) # %25 is "%"
|
||||
self.assertEqual(self.titles("q=e_D"), ["100% Pure"]) # literal "_" in Jane_Doe
|
||||
self.assertEqual(
|
||||
self.titles("q=k_s"), []
|
||||
) # unescaped "_" would match "Back\\slash"
|
||||
self.assertEqual(self.titles("q=k%5Cs"), ["1000 Pages"]) # %5C is "\"
|
||||
self.assertEqual(self.titles("q=100%25"), ["100% Pure"])
|
||||
self.assertEqual(self.titles("q=e_D"), ["100% Pure"])
|
||||
self.assertEqual(self.titles("q=k_s"), [])
|
||||
self.assertEqual(self.titles("q=k%5Cs"), ["1000 Pages"])
|
||||
|
||||
def test_genre_filter_and_combination(self):
|
||||
self.assertEqual(
|
||||
|
||||
@@ -51,7 +51,6 @@ def text(body: dict, field: str, max_len: int) -> str:
|
||||
|
||||
def integer(body: dict, field: str, low: int, high: int) -> int:
|
||||
value = body.get(field)
|
||||
# bool is a subclass of int in Python; JSON true must not count as 1.
|
||||
if isinstance(value, bool) or not isinstance(value, int):
|
||||
raise ApiError(400, f"{field} must be a whole number", field)
|
||||
if not low <= value <= high:
|
||||
|
||||
Reference in New Issue
Block a user