kaledonya clips: a Discord-friendly clip site on top of Zipline

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
esefandClaude Opus 5.5 committed 2026-10-07 20:46:48 +03:00
commit e6f8905377
34 files changed
+2917

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
import pytest
from app.names import clean_name, next_free_name, split_name
@pytest.mark.parametrize(
"raw, expected",
[
("2026-10-01 21-33-07.mkv", "2026-10-01-21-33-07.mkv"),
("my clip\tfinal.mp4", "my-clip-final.mp4"),
(" padded.mp4 ", "padded.mp4"),
("C:\\Users\\alice\\Videos\\a b.mp4", "a-b.mp4"),
("dir/sub/x y.png", "x-y.png"),
("çılgın an #1.mp4", "çılgın-an-#1.mp4"),
("", "upload"),
(" ", "upload"),
],
)
def test_clean_name(raw, expected):
assert clean_name(raw) == expected
def test_split_name():
assert split_name("clip.mp4") == ("clip", ".mp4")
assert split_name("a.b.mkv") == ("a.b", ".mkv")
def test_split_name_edge_cases():
assert split_name("README") == ("README", "")
assert split_name(".mp4") == (".mp4", "")
def test_next_free_name_free():
assert next_free_name("clip", ".mp4", lambda n: False) == "clip.mp4"
def test_next_free_name_suffixes():
taken = {"clip.mp4", "clip-2.mp4"}
assert next_free_name("clip", ".mp4", taken.__contains__) == "clip-3.mp4"
def test_next_free_name_no_extension():
assert next_free_name("README", "", {"README"}.__contains__) == "README-2"
def test_next_free_name_gives_up():
with pytest.raises(RuntimeError):
next_free_name("x", ".mp4", lambda n: True, limit=5)
+161
View File
@@ -0,0 +1,161 @@
import httpx
import pytest
from fastapi.testclient import TestClient
from app.previews import (
LONG_SECONDS,
Previews,
encode_cmd,
follow_progress,
niced,
target_bytes,
video_kbps,
)
from app.server import Settings, create_app
from tests.test_server import FakeZipline as UploadZipline
from tests.test_site_api import FakeZipline, login, mkfile
def test_bitrate_fits_the_target():
for seconds in (30, 70, 300):
kbps = video_kbps(seconds)
assert (kbps + 128) * 1000 / 8 * seconds <= target_bytes(seconds)
assert video_kbps(10) == 4000 # short clips are capped, not blown up
assert video_kbps(3600) is None # an hour can't fit in 22 MB at any watchable quality
assert video_kbps(0) is None
def test_long_clips_get_a_smaller_file():
assert target_bytes(LONG_SECONDS) == 22_000_000
assert target_bytes(LONG_SECONDS + 1) == 18_000_000
def test_encode_keeps_one_audio_track_and_720p_up_to_four_minutes():
cmd = encode_cmd("http://zipline:3000/a.mp4", "out.mp4", 600, LONG_SECONDS)
assert cmd[cmd.index("-map") + 1:cmd.index("-map") + 4] == ["0:v:0", "-map", "0:a:0?"]
assert "scale=-2:'min(720,ih)'" in cmd and "+faststart" in cmd
assert "scale=-2:'min(480,ih)'" in encode_cmd("x", "y", 600, LONG_SECONDS + 1)
def test_encode_runs_at_low_priority_when_nice_exists(monkeypatch):
monkeypatch.setattr("app.previews.shutil.which", lambda name: "/usr/bin/nice")
assert niced(["ffmpeg", "-i", "x"]) == ["nice", "-n", "10", "ffmpeg", "-i", "x"]
monkeypatch.setattr("app.previews.shutil.which", lambda name: None)
assert niced(["ffmpeg"]) == ["ffmpeg"]
def test_small_clips_get_no_copy(tmp_path, monkeypatch):
p = Previews(tmp_path, "http://zipline:3000", over_bytes=1000)
monkeypatch.setattr(p, "make", lambda *a: pytest.fail("should not encode"))
p.request("small.mp4", 999)
assert p.jobs.empty() and not p.pending
def test_failed_clip_is_not_retried(tmp_path):
p = Previews(tmp_path, "http://zipline:3000", over_bytes=10)
(tmp_path / f"{Previews.key('big.mp4', 50)}.failed").touch()
p.request("big.mp4", 50)
assert p.jobs.empty()
def test_key_changes_with_size_and_rejects_paths(tmp_path):
assert Previews.key("a.mp4", 1) != Previews.key("a.mp4", 2)
p = Previews(tmp_path, "http://zipline:3000", over_bytes=10)
assert p.file("../secret") is None
@pytest.fixture
def setup(tmp_path, monkeypatch):
zl = FakeZipline()
zl.files["tok-alice"].append(mkfile("e9", "big.mp4", size=5000))
asked = []
monkeypatch.setattr(Previews, "request", lambda self, name, length: asked.append((name, length)))
monkeypatch.setattr("app.site_api.video_size", lambda url, length: (1280, 720))
settings = Settings(zipline_url="http://zipline:3000", site_origin="https://clips.example.com",
upload_url="https://upload.example.com", max_bytes=10_000, work_dir=tmp_path, preview_over=1000)
client = TestClient(create_app(settings, transport=httpx.MockTransport(zl.handler)), base_url="https://testserver")
return client, asked, tmp_path / "previews"
def test_big_clip_without_copy_links_the_original_and_asks_for_one(setup):
client, asked, _ = setup
r = client.get("/v/big.mp4")
assert '<meta property="og:video" content="https://clips.example.com/big.mp4">' in r.text
assert asked == [("big.mp4", 5000)]
def test_big_clip_with_copy_links_the_copy(setup):
client, asked, folder = setup
key = Previews.key("big.mp4", 5000)
folder.mkdir()
(folder / f"{key}.mp4").write_bytes(b"0123456789")
r = client.get("/v/big.mp4")
assert f'<meta property="og:video" content="https://clips.example.com/_site/preview/{key}.mp4">' in r.text
assert '<meta property="og:video:width" content="1280">' in r.text
assert asked == []
got = client.get(f"/_site/preview/{key}.mp4", headers={"range": "bytes=2-5"})
assert got.status_code == 206 and got.content == b"2345"
assert got.headers["content-type"] == "video/mp4"
assert client.get("/_site/preview/" + "0" * 24 + ".mp4").status_code == 404
def test_upload_of_a_video_asks_for_a_copy(tmp_path, monkeypatch):
asked = []
monkeypatch.setattr(Previews, "request", lambda self, name, length: asked.append((name, length)))
settings = Settings(zipline_url="http://zipline:3000", site_origin="https://clips.example.com",
upload_url="https://upload.example.com", max_bytes=10_000, work_dir=tmp_path, preview_over=1000)
client = TestClient(create_app(settings, transport=httpx.MockTransport(UploadZipline().handler)))
for name, kind in (("clip.mp4", "video/mp4"), ("notes.zip", "application/zip")):
r = client.post("/upload", headers={"authorization": "tok"}, files={"file": (name, b"x" * 1500, kind)})
assert r.status_code == 200
assert asked == [("clip.mp4", 1500)]
def test_rename_carries_the_copy_over(setup):
client, _, folder = setup
folder.mkdir()
(folder / f"{Previews.key('big.mp4', 5000)}.mp4").write_bytes(b"copy")
login(client)
assert client.patch("/_site/api/files/e9", json={"name": "renamed.mp4"}).json() == {"name": "renamed.mp4"}
assert (folder / f"{Previews.key('renamed.mp4', 5000)}.mp4").read_bytes() == b"copy"
assert not (folder / f"{Previews.key('big.mp4', 5000)}.mp4").exists()
r = client.get("/v/renamed.mp4")
assert f"/_site/preview/{Previews.key('renamed.mp4', 5000)}.mp4" in r.text
def test_discord_status_tells_the_site_when_the_link_is_safe(setup):
client, asked, folder = setup
get = lambda name: client.get(f"/_site/api/discord/{name}").json()
assert get("clutch.mp4") == {"state": "small", "progress": 1.0, "queued": False} # plays as is
assert get("big.mp4") == {"state": "making", "progress": 0.0, "queued": True}
assert asked == [("big.mp4", 5000)]
folder.mkdir()
key = Previews.key("big.mp4", 5000)
(folder / f"{key}.failed").touch()
assert get("big.mp4")["state"] == "none"
(folder / f"{key}.mp4").write_bytes(b"copy")
assert get("big.mp4") == {"state": "ready", "progress": 1.0, "queued": False}
assert get("nope.mp4")["state"] == "small"
def test_status_shows_the_encode_progress_and_the_queue(tmp_path):
p = Previews(tmp_path, "http://zipline:3000", over_bytes=10)
p.worker = type("Busy", (), {"is_alive": lambda self: True})() # a running worker: request() only queues
p.request("a.mp4", 50)
p.request("b.mp4", 50)
p.current, p.progress = Previews.key("a.mp4", 50), 0.42
assert p.status("a.mp4", 50) == {"state": "making", "progress": 0.42, "queued": False}
assert p.status("b.mp4", 50) == {"state": "making", "progress": 0.0, "queued": True}
def test_follow_progress_reads_ffmpeg_progress_lines():
seen = []
lines = ["frame=10", "out_time_us=5000000", "progress=continue", "out_time_us=N/A",
"out_time_us=9000000", "out_time_us=12000000", "progress=end"]
follow_progress(lines, 10.0, seen.append)
assert seen == [0.5, 0.9, 1.0]
def test_encode_reports_progress():
cmd = encode_cmd("x", "y", 600, 30)
assert cmd[cmd.index("-progress") + 1] == "pipe:1" and "-nostats" in cmd
+40
View File
@@ -0,0 +1,40 @@
import shutil
import subprocess
import pytest
from app.remux import remux_to_mp4
needs_ffmpeg = pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg not installed")
def make_mkv(path):
subprocess.run(
["ffmpeg", "-v", "error", "-y",
"-f", "lavfi", "-i", "testsrc=size=320x240:rate=30",
"-f", "lavfi", "-i", "sine=frequency=440",
"-t", "1", "-c:v", "libx264", "-c:a", "aac", str(path)],
check=True,
)
@needs_ffmpeg
def test_remux_good_mkv(tmp_path):
src, dst = tmp_path / "in.mkv", tmp_path / "out.mp4"
make_mkv(src)
assert remux_to_mp4(src, dst) is True
assert dst.read_bytes()[4:8] == b"ftyp"
def test_remux_garbage_returns_false(tmp_path):
src, dst = tmp_path / "in.mkv", tmp_path / "out.mp4"
src.write_bytes(b"not a video at all")
assert remux_to_mp4(src, dst) is False
assert not dst.exists()
def test_remux_missing_ffmpeg_returns_false(tmp_path, monkeypatch):
monkeypatch.setenv("PATH", "")
src, dst = tmp_path / "in.mkv", tmp_path / "out.mp4"
src.write_bytes(b"x")
assert remux_to_mp4(src, dst) is False
+253
View File
@@ -0,0 +1,253 @@
import shutil
import subprocess
from urllib.parse import quote, unquote
import httpx
import pytest
from fastapi.testclient import TestClient
from app.server import Settings, create_app
class FakeZipline:
def __init__(self):
self.names = set() # names Zipline already has
self.race = set() # free at HEAD time, taken by the time we upload
self.fail_with = None # force this status on /api/upload
self.raw_urls = False # return the url unencoded, like a careless server would
self.head_fails = False # HEAD raises, as if Zipline went away mid-request
self.uploads = []
def handler(self, request: httpx.Request) -> httpx.Response:
if request.method == "HEAD" and self.head_fails:
raise httpx.ConnectError("down")
if request.method == "HEAD":
name = unquote(request.url.path[1:])
if name in self.names:
# Zipline redirects text files (and password-protected ones) to /view/<name>
return httpx.Response(302 if name.endswith(".txt") else 200)
return httpx.Response(404)
if request.url.path == "/api/upload":
if self.fail_with:
return httpx.Response(self.fail_with, json={"error": "boom"})
stem = unquote(request.headers["x-zipline-filename"])
name = stem + request.headers.get("x-zipline-file-extension", "")
if name in self.names or name in self.race:
self.race.discard(name)
self.names.add(name)
return httpx.Response(400, json={"error": "file with the same name already exists"})
self.names.add(name)
self.uploads.append({"name": name, "headers": dict(request.headers), "body": request.read()})
return httpx.Response(200, json={"files": [{
"id": "1", "name": name, "type": "video/mp4",
"url": f"https://clips.example.com/{name if self.raw_urls else quote(name)}",
}]})
return httpx.Response(404)
@pytest.fixture
def zl():
return FakeZipline()
@pytest.fixture
def client(zl, tmp_path):
settings = Settings(zipline_url="http://zipline:3000", site_origin="https://clips.example.com",
upload_url="https://upload.example.com", max_bytes=1000, deletes_at="90d", work_dir=tmp_path)
return TestClient(create_app(settings, transport=httpx.MockTransport(zl.handler)))
AUTH = {"Authorization": "tok"}
def up(client, name, data=b"x" * 10, headers=AUTH):
return client.post("/upload", files={"file": (name, data, "application/octet-stream")}, headers=headers)
def test_health(client):
assert client.get("/health").json() == {"ok": True}
def test_missing_token_is_401(client, zl):
r = up(client, "a.mp4", headers={})
assert r.status_code == 401
assert zl.uploads == []
def test_missing_file_field_is_400(client):
r = client.post("/upload", data={"nope": "1"}, headers=AUTH)
assert r.status_code == 400
def test_spaces_become_dashes_and_headers(client, zl):
r = up(client, "my clip 1.mp4")
assert r.status_code == 200
assert r.json() == {"url": "https://clips.example.com/my-clip-1.mp4", "name": "my-clip-1.mp4", "remuxed": False}
h = zl.uploads[0]["headers"]
assert h["authorization"] == "tok"
assert h["x-zipline-format"] == "name"
assert h["x-zipline-filename"] == "my-clip-1"
assert h["x-zipline-file-extension"] == ".mp4"
assert h["x-zipline-deletes-at"] == "90d"
assert h["x-zipline-original-name"] == "true"
assert zl.uploads[0]["body"].count(b"x" * 10) == 1
def test_duplicate_gets_suffix(client, zl):
zl.names |= {"clip.mp4", "clip-2.mp4"}
assert up(client, "clip.mp4").json()["name"] == "clip-3.mp4"
def test_taken_text_file_redirect_counts_as_taken(client, zl):
zl.names |= {"n.txt", "n-2.txt", "n-3.txt"}
r = up(client, "n.txt")
assert r.status_code == 200
assert r.json()["name"] == "n-4.txt"
def test_race_retries_next_name(client, zl):
zl.race.add("clip.mp4")
r = up(client, "clip.mp4")
assert r.status_code == 200
assert r.json()["name"] == "clip-2.mp4"
def test_unicode_and_special_chars(client, zl):
r = up(client, "çılgın an #1.mp4")
assert r.status_code == 200
assert r.json()["name"] == "çılgın-an-#1.mp4"
assert zl.uploads[0]["headers"]["x-zipline-filename"] == quote("çılgın-an-#1")
def test_no_extension(client, zl):
r = up(client, "README")
assert r.json()["name"] == "README"
assert "x-zipline-file-extension" not in zl.uploads[0]["headers"]
def test_exact_limit_ok(client):
assert up(client, "a.bin", data=b"x" * 1000).status_code == 200
def test_one_byte_over_is_413(client, zl):
r = up(client, "a.bin", data=b"x" * 1001)
assert r.status_code == 413
assert r.json()["error"] == f"file is larger than the {1000 / 1024**3:g} GB limit"
assert zl.uploads == []
def test_huge_content_length_rejected_early(client, zl):
r = client.post("/upload", content=b"x", headers={**AUTH, "content-length": str(10**12),
"content-type": "multipart/form-data; boundary=b"})
assert r.status_code == 413
def test_zipline_error_is_502_and_cleans_up(client, zl, tmp_path):
zl.fail_with = 500
r = up(client, "a b.mkv", data=b"garbage")
assert r.status_code == 502
assert list(tmp_path.iterdir()) == []
def test_zipline_4xx_passed_through(client, zl):
zl.fail_with = 401
assert up(client, "a.mp4").status_code == 401
def test_bad_mkv_kept_as_mkv(client, zl):
r = up(client, "a b.mkv", data=b"not a video")
assert r.status_code == 200
assert r.json() == {"url": "https://clips.example.com/a-b.mkv", "name": "a-b.mkv", "remuxed": False}
@pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg not installed")
def test_good_mkv_becomes_mp4(zl, tmp_path):
src = tmp_path / "src.mkv"
subprocess.run(["ffmpeg", "-v", "error", "-y", "-f", "lavfi", "-i", "testsrc=size=320x240:rate=30",
"-t", "1", "-c:v", "libx264", str(src)], check=True)
work = tmp_path / "work"
work.mkdir()
settings = Settings(zipline_url="http://zipline:3000", site_origin="https://clips.example.com",
upload_url="https://upload.example.com", max_bytes=10**8, deletes_at="90d", work_dir=work)
c = TestClient(create_app(settings, transport=httpx.MockTransport(zl.handler)))
r = up(c, "my clip.mkv", data=src.read_bytes())
assert r.json()["name"] == "my-clip.mp4"
assert r.json()["remuxed"] is True
assert zl.uploads[0]["headers"]["x-zipline-file-extension"] == ".mp4"
assert list(work.iterdir()) == []
def test_generic_content_type_is_guessed_from_extension(client, zl):
# curl/ShareX send application/octet-stream; Discord only plays video/mp4 inline
assert up(client, "clip.mp4").status_code == 200
assert b"Content-Type: video/mp4" in zl.uploads[0]["body"]
def test_unknown_extension_stays_octet_stream(client, zl):
assert up(client, "data.zzz9").status_code == 200
assert b"Content-Type: application/octet-stream" in zl.uploads[0]["body"]
def test_upload_with_ticket(client, zl):
ticket = client.app.state.tickets.issue("tok-from-ticket")
r = up(client, "t.mp4", headers={"x-upload-ticket": ticket})
assert r.status_code == 200
assert zl.uploads[0]["headers"]["authorization"] == "tok-from-ticket"
def test_ticket_single_use(client, zl):
ticket = client.app.state.tickets.issue("tok")
assert up(client, "a.mp4", headers={"x-upload-ticket": ticket}).status_code == 200
r = up(client, "b.mp4", headers={"x-upload-ticket": ticket})
assert r.status_code == 401
assert r.json() == {"error": "upload ticket expired, try again"}
assert len(zl.uploads) == 1
def test_bad_ticket_is_401(client, zl):
assert up(client, "a.mp4", headers={"x-upload-ticket": "made-up"}).status_code == 401
assert zl.uploads == []
def test_cors_preflight_for_site_origin(client):
r = client.options("/upload", headers={
"origin": "https://clips.example.com",
"access-control-request-method": "POST",
"access-control-request-headers": "x-upload-ticket",
})
assert r.status_code == 200
assert r.headers["access-control-allow-origin"] == "https://clips.example.com"
assert "x-upload-ticket" in r.headers["access-control-allow-headers"].lower()
def test_cors_refuses_other_origin(client):
r = client.options("/upload", headers={
"origin": "https://evil.example",
"access-control-request-method": "POST",
})
assert "access-control-allow-origin" not in r.headers
def test_cors_headers_on_error_responses(client):
r = up(client, "a.bin", data=b"x" * 1001, headers={**AUTH, "origin": "https://clips.example.com"})
assert r.status_code == 413
assert r.headers["access-control-allow-origin"] == "https://clips.example.com"
def test_link_is_built_from_the_name_not_ziplines_url(client, zl):
# a '#' left unencoded in Zipline's url would cut the shared link off
zl.raw_urls = True
r = up(client, "çılgın an #1.mp4")
assert r.json()["url"] == "https://clips.example.com/" + quote("çılgın-an-#1.mp4")
def test_zipline_down_during_name_check_is_502(client, zl):
zl.head_fails = True
r = up(client, "a.mp4")
assert r.status_code == 502
def test_no_free_name_is_409(client, zl, monkeypatch):
from app import server
monkeypatch.setattr(server, "next_free_name", lambda *a, **k: (_ for _ in ()).throw(RuntimeError("full")))
assert up(client, "a.mp4").status_code == 409
+382
View File
@@ -0,0 +1,382 @@
import json
import math
import re
from urllib.parse import quote, unquote
import httpx
import pytest
from fastapi.testclient import TestClient
from app.server import Settings, create_app
def mkfile(fid, name, size=100, thumb=True):
return {
"id": fid, "name": name, "originalName": name, "size": size, "type": "video/mp4", "views": 3,
"createdAt": "2026-10-01T16:46:15.378Z", "deletesAt": "2026-12-30T16:46:15.375Z",
"thumbnail": {"path": f".thumbnail.{fid}.jpg"} if thumb else None, "tags": [], "url": "/" + name,
}
class FakeZipline:
def __init__(self):
self.users = {"alice": ("pw", "tok-alice"), "bob": ("pw2", "tok-bob")}
self.files = {
"tok-alice": [mkfile("e1", "clutch.mp4"), mkfile("e2", "çılgın-#1.mp4", thumb=False)],
"tok-bob": [mkfile("c1", "friend.mp4")],
}
self.down = False
self.calls = []
def owner(self, tok):
return next((name for name, (_, t) in self.users.items() if t == tok), None)
def handler(self, request: httpx.Request) -> httpx.Response:
if self.down:
raise httpx.ConnectError("down")
path, method = request.url.path, request.method
self.calls.append((method, path, request.headers.get("authorization"), request.headers.get("cookie")))
if path == "/api/auth/login":
body = json.loads(request.content)
user = self.users.get(body["username"])
if not user or user[0] != body["password"]:
return httpx.Response(400, json={"error": "E1044: Invalid username or password"})
return httpx.Response(
200, json={"user": {"id": "u-" + body["username"], "username": body["username"]}},
headers={"set-cookie": f"zipline_session=sess-{body['username']}; Max-Age=1209600; Path=/; HttpOnly; Secure; SameSite=Lax"},
)
if path == "/api/user/token":
cookie = request.headers.get("cookie") or ""
for name, (_, tok) in self.users.items():
if f"zipline_session=sess-{name}" in cookie:
return httpx.Response(200, json={"token": tok})
return httpx.Response(401, json={"error": "E2001: Invalid token"})
if method == "HEAD":
name = unquote(path[1:])
f = next((f for files in self.files.values() for f in files if f["name"] == name), None)
if f is None:
return httpx.Response(404)
return httpx.Response(200, headers={"content-type": f["type"], "content-length": str(f["size"])})
tok = request.headers.get("authorization")
who = self.owner(tok)
if who is None:
return httpx.Response(401, json={"error": "E2001: Invalid token"})
mine = self.files.setdefault(tok, [])
if path == "/api/user":
return httpx.Response(200, json={"user": {"id": "u-" + who, "username": who, "role": "USER"}})
if path == "/api/user/stats":
return httpx.Response(200, json={"storageUsed": sum(f["size"] for f in mine)})
if path == "/api/user/files":
page = int(request.url.params.get("page", "1"))
per = int(request.url.params.get("perpage", "15"))
chunk = mine[(page - 1) * per: page * per]
return httpx.Response(200, json={"page": chunk, "total": len(mine), "pages": max(1, math.ceil(len(mine) / per))})
if path.startswith("/api/user/files/"):
ident = unquote(path[len("/api/user/files/"):])
f = next((f for f in mine if ident in (f["id"], f["name"])), None)
if f is None:
return httpx.Response(404, json={"error": "E4000: File not found"})
if method == "GET":
return httpx.Response(200, json=f)
if method == "PATCH":
f["name"] = json.loads(request.content)["name"]
return httpx.Response(200, json=f)
if method == "DELETE":
mine.remove(f)
return httpx.Response(200, json=f)
return httpx.Response(404, json={"error": "not found"})
@pytest.fixture
def zl():
return FakeZipline()
@pytest.fixture
def client(zl, tmp_path):
settings = Settings(zipline_url="http://zipline:3000", site_origin="https://clips.example.com",
upload_url="https://upload.example.com", max_bytes=1000, deletes_at="90d", work_dir=tmp_path)
return TestClient(create_app(settings, transport=httpx.MockTransport(zl.handler)), base_url="https://testserver")
def login(client, user="alice", pw="pw", ip="1.2.3.4"):
return client.post("/_site/api/login", json={"username": user, "password": pw}, headers={"cf-connecting-ip": ip})
def test_pages_serve_the_site(client):
for path in ("/", "/v/clutch.mp4", "/v/%C3%A7%C4%B1lg%C4%B1n-%231.mp4"):
r = client.get(path)
assert r.status_code == 200
assert "text/html" in r.headers["content-type"]
assert "/_site/static/app.js" in r.text
assert client.get("/_site/static/style.css").status_code == 200
@pytest.fixture
def fake_media(monkeypatch):
seen = []
monkeypatch.setattr("app.site_api.video_size", lambda url, length: seen.append(url) or (1920, 1080))
monkeypatch.setattr("app.site_api.still_jpeg", lambda url, length: seen.append(url) or b"jpeg-bytes")
return seen
def test_watch_page_has_discord_player_tags(client, fake_media):
# Discord gives up on big raw links; the /v/ page hands it the player URL, size and a still instead
r = client.get("/v/%C3%A7%C4%B1lg%C4%B1n-%231.mp4")
clip = "https://clips.example.com/%C3%A7%C4%B1lg%C4%B1n-%231.mp4"
assert f'<meta property="og:video" content="{clip}">' in r.text
assert '<meta property="og:video:type" content="video/mp4">' in r.text
assert '<meta property="og:video:width" content="1920">' in r.text
assert '<meta property="og:video:height" content="1080">' in r.text
assert ('<meta property="og:image" content="https://clips.example.com/_site/thumb/'
'%C3%A7%C4%B1lg%C4%B1n-%231.mp4">') in r.text
assert fake_media == ["http://zipline:3000/%C3%A7%C4%B1lg%C4%B1n-%231.mp4"]
def test_shared_clip_gets_a_random_stripe_color(client, fake_media):
colors = {re.search(r'name="theme-color" content="(#[0-9a-f]{6})"', client.get("/v/clutch.mp4").text).group(1)
for _ in range(5)}
assert len(colors) > 1 and "#3e4637" not in colors
assert 'name="theme-color" content="#3e4637"' in client.get("/").text
def test_watch_page_without_clip_has_no_player_tags(client, fake_media):
for path in ("/v/nope.mp4", '/v/"><script>alert(1)</script>'):
r = client.get(path)
assert r.status_code == 200
assert "og:video" not in r.text and "<script>alert" not in r.text
assert fake_media == []
def test_title_is_escaped(zl, client, fake_media):
zl.files["tok-alice"].append(mkfile("e3", 'a"><b>.mp4'))
r = client.get("/v/" + quote('a"><b>.mp4'))
assert '<meta property="og:title" content="a&quot;&gt;&lt;b&gt;.mp4">' in r.text
assert '"><b>' not in r.text
def test_thumbnail(client, fake_media):
r = client.get("/_site/thumb/clutch.mp4")
assert r.status_code == 200 and r.headers["content-type"] == "image/jpeg"
assert r.content == b"jpeg-bytes"
assert client.get("/_site/thumb/nope.mp4").status_code == 404
def test_public_file_needs_no_login(client):
r = client.get("/_site/api/public/clutch.mp4")
assert r.status_code == 200
assert r.json() == {"name": "clutch.mp4", "type": "video/mp4", "size": 100, "url": "/clutch.mp4",
"thumbnail": "/_site/thumb/clutch.mp4"}
assert r.headers["cache-control"] == "no-store, private"
assert client.get("/_site/api/public/nope.mp4").status_code == 404
def test_public_file_when_zipline_is_down(zl, client):
zl.down = True
assert client.get("/_site/api/public/clutch.mp4").status_code == 502
def test_config(client):
assert client.get("/_site/api/config").json() == {
"uploadUrl": "https://upload.example.com", "publicUrl": "https://clips.example.com",
"uploadHint": "Can't reach the upload server."}
def test_site_and_server_names_reach_the_page(zl, tmp_path, fake_media):
settings = Settings(zipline_url="http://zipline:3000", site_origin="https://clips.example.com",
site_name='my <clips>', server_name='box"1', work_dir=tmp_path)
client = TestClient(create_app(settings, transport=httpx.MockTransport(zl.handler)), base_url="https://testserver")
page = client.get("/").text
assert "<title>my &lt;clips&gt;</title>" in page
assert 'data-server="box&quot;1"' in page
assert '<meta property="og:site_name" content="my &lt;clips&gt;">' in client.get("/v/clutch.mp4").text
def test_login_sets_cookie(client):
r = login(client)
assert r.status_code == 200
assert r.json() == {"username": "alice"}
sc = r.headers["set-cookie"]
assert "ys=tok-alice" in sc
for flag in ("HttpOnly", "Secure", "SameSite=lax", "Max-Age=2592000", "Path=/"):
assert flag in sc
def test_login_wrong_password(client):
r = login(client, pw="nope")
assert r.status_code == 401
assert r.json() == {"error": "Wrong username or password."}
assert "ys=" not in r.headers.get("set-cookie", "")
def test_login_rate_limit_per_ip(client):
for _ in range(5):
assert login(client, pw="nope").status_code == 401
r = login(client)
assert r.status_code == 429
assert r.json() == {"error": "Too many tries, wait a few minutes."}
assert login(client, ip="9.9.9.9").status_code == 200
def test_login_zipline_down(client, zl):
zl.down = True
assert login(client).status_code == 502
def test_two_logins_get_own_tokens(client):
login(client, "alice", "pw")
r = login(client, "bob", "pw2")
assert "ys=tok-bob" in r.headers["set-cookie"]
assert client.get("/_site/api/me").json()["username"] == "bob"
def test_me_requires_login(client):
assert client.get("/_site/api/me").status_code == 401
def test_me(client):
login(client)
assert client.get("/_site/api/me").json() == {"username": "alice", "storageUsed": 200}
def test_revoked_token_clears_cookie(client):
client.cookies.set("ys", "revoked", domain="testserver")
r = client.get("/_site/api/files")
assert r.status_code == 401
assert 'ys=""' in r.headers["set-cookie"] or "ys=;" in r.headers["set-cookie"]
assert "Max-Age=0" in r.headers["set-cookie"]
def test_logout(client):
login(client)
r = client.post("/_site/api/logout")
assert r.json() == {"ok": True}
assert "Max-Age=0" in r.headers["set-cookie"]
assert client.get("/_site/api/me").status_code == 401
def test_files_list(client, zl):
login(client)
d = client.get("/_site/api/files").json()
assert d["total"] == 2 and d["pages"] == 1
first = d["files"][0]
assert first == {
"id": "e1", "name": "clutch.mp4", "size": 100, "type": "video/mp4", "views": 3,
"createdAt": "2026-10-01T16:46:15.378Z", "deletesAt": "2026-12-30T16:46:15.375Z",
"url": "/clutch.mp4", "thumbnail": "/raw/.thumbnail.e1.jpg",
}
assert d["files"][1]["url"] == "/" + quote("çılgın-#1.mp4")
assert d["files"][1]["thumbnail"] is None
sent = [c for c in zl.calls if c[1] == "/api/user/files"][0]
assert sent[2] == "tok-alice"
def test_files_paging(client, zl):
zl.files["tok-alice"] = [mkfile(f"f{i}", f"c{i}.mp4") for i in range(30)]
login(client)
p1 = client.get("/_site/api/files?page=1").json()
p2 = client.get("/_site/api/files?page=2").json()
assert len(p1["files"]) == 24 and p1["pages"] == 2
assert len(p2["files"]) == 6
def test_friend_cannot_see_alice_file(client):
login(client, "bob", "pw2")
r = client.get("/_site/api/files/e1")
assert r.status_code == 404
assert r.json() == {"error": "This clip doesn't exist (or isn't yours)."}
def test_file_by_unicode_name(client):
login(client)
r = client.get("/_site/api/files/" + quote("çılgın-#1.mp4"))
assert r.status_code == 200
assert r.json()["id"] == "e2"
def test_rename(client, zl):
login(client)
r = client.patch("/_site/api/files/e1", json={"name": "best clip ever.mp4"})
assert r.json() == {"name": "best-clip-ever.mp4"}
assert zl.files["tok-alice"][0]["name"] == "best-clip-ever.mp4"
def test_rename_strips_path(client, zl):
login(client)
assert client.patch("/_site/api/files/e1", json={"name": "../x y.mp4"}).json() == {"name": "x-y.mp4"}
def test_rename_taken(client, zl):
login(client)
r = client.patch("/_site/api/files/e1", json={"name": "friend.mp4"})
assert r.status_code == 409
assert r.json() == {"error": "That name is already used."}
assert not [c for c in zl.calls if c[0] == "PATCH"]
def test_delete(client, zl):
login(client)
assert client.delete("/_site/api/files/e1").json() == {"ok": True}
assert [f["id"] for f in zl.files["tok-alice"]] == ["e2"]
def test_files_zipline_down(client, zl):
login(client)
zl.down = True
r = client.get("/_site/api/files")
assert r.status_code == 502
assert r.json() == {"error": "Zipline isn't reachable right now."}
def test_upload_ticket(client):
assert client.post("/_site/api/upload-ticket").status_code == 401
login(client)
d = client.post("/_site/api/upload-ticket").json()
assert d["uploadUrl"] == "https://upload.example.com"
assert client.app.state.tickets.redeem(d["ticket"]) == "tok-alice"
def test_assets_are_versioned_and_page_not_cached(client):
# Cloudflare keeps .js/.css in browsers for 4 hours; a content hash in the URL makes updates show up at once
r = client.get("/")
assert r.headers["cache-control"] == "no-store"
assert client.get("/v/clip.mp4").headers["cache-control"] == "no-store"
assert "/_site/static/app.js?v=" in r.text
assert "/_site/static/style.css?v=" in r.text
assert "{{v}}" not in r.text
def test_api_answers_are_never_cached(client):
# Cloudflare caches anything ending in .mp4 by extension; a cached clip lookup would leak between users
for r in (client.get("/_site/api/files/clutch.mp4"), login(client), client.get("/_site/api/files/clutch.mp4"),
client.get("/_site/api/files/nope.mp4"), client.get("/_site/api/me"), client.get("/_site/api/config")):
assert r.headers.get("cache-control") == "no-store, private"
def test_login_limit_uses_forwarded_for_without_cloudflare(client):
# over the Tailscale address there's no CF-Connecting-IP; tailscale serve passes the tailnet IP in X-Forwarded-For
def tailnet_login(ip, pw):
return client.post("/_site/api/login", json={"username": "alice", "password": pw},
headers={"x-forwarded-for": ip})
for _ in range(5):
tailnet_login("100.64.0.5", "nope")
assert tailnet_login("100.64.0.5", "pw").status_code == 429
assert tailnet_login("100.64.0.9", "pw").status_code == 200
def test_other_account_login_doesnt_reset_the_limit(client):
# bob guessing alice's password can't clear their strikes by logging in as themselves
for _ in range(4):
login(client, "alice", "nope")
assert login(client, "bob", "pw2").status_code == 200
login(client, "alice", "nope")
assert login(client, "alice", "pw").status_code == 429
def test_empty_or_nested_file_id_is_404(client, zl):
login(client)
before = len(zl.calls)
for r in (client.get("/_site/api/files/"), client.delete("/_site/api/files/a%2Fb"),
client.patch("/_site/api/files/x/transaction", json={"name": "y.mp4"})):
assert r.status_code == 404
assert len(zl.calls) == before
+66
View File
@@ -0,0 +1,66 @@
from app.site_auth import LoginLimiter, TicketStore
class Clock:
def __init__(self):
self.t = 1000.0
def __call__(self):
return self.t
def test_limiter_blocks_after_limit():
lim = LoginLimiter(limit=3, window=60, now=Clock())
for _ in range(3):
assert not lim.blocked("ip")
lim.fail("ip")
assert lim.blocked("ip")
assert not lim.blocked("other-ip")
def test_limiter_forgets_expired_keys():
# made-up usernames make a new key per guess; once their strikes expire they're dropped
clock = Clock()
lim = LoginLimiter(limit=5, window=60, now=clock)
for i in range(100):
lim.fail(f"ip|user{i}")
clock.t += 61
lim.fail("ip|someone")
assert list(lim.fails) == ["ip|someone"]
def test_limiter_window_slides():
clock = Clock()
lim = LoginLimiter(limit=2, window=60, now=clock)
lim.fail("ip")
lim.fail("ip")
assert lim.blocked("ip")
clock.t += 61
assert not lim.blocked("ip")
def test_limiter_reset():
lim = LoginLimiter(limit=1, window=60, now=Clock())
lim.fail("ip")
lim.reset("ip")
assert not lim.blocked("ip")
def test_ticket_redeems_once():
store = TicketStore(ttl=60, now=Clock())
t = store.issue("tok")
assert len(t) >= 32
assert store.redeem(t) == "tok"
assert store.redeem(t) is None
def test_ticket_expires():
clock = Clock()
store = TicketStore(ttl=60, now=clock)
t = store.issue("tok")
clock.t += 61
assert store.redeem(t) is None
def test_unknown_ticket():
assert TicketStore().redeem("nope") is None