import threading
import json
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
import httpx
from peach import http as peach_http
from peach.follow_stream import (
MAX_PROXY_REDIRECTS, FollowMediaResolver, FollowMediaUnavailable,
FollowProxyError, ResolvedFollowMedia, open_upstream, proxy_request_headers,
proxyable,
)
from peach.follow_secrets import Credential
from peach.http import HttpResponse
class FollowMediaResolverTests(unittest.TestCase):
def test_source_cooldown_survives_resolver_restart_and_expires(self):
moment, wall = [1000.0], [11000.0]
transport = mock.Mock()
with tempfile.TemporaryDirectory() as temporary:
options = {'state_root': Path(temporary), 'clock': lambda: moment[0],
'wall_clock': lambda: wall[0]}
first = FollowMediaResolver(transport, **options)
first.pause('iwara', 120)
wall[0] += 30
moment[0] = 5
second = FollowMediaResolver(transport, **options)
self.assertEqual(second.cooling('iwara'), 90)
moment[0] += 91
wall[0] += 91
self.assertEqual(FollowMediaResolver(transport, **options).cooling('iwara'), 0)
transport.assert_not_called()
def test_iwara_transport_cleanup_preserves_the_callers_transport(self):
owned = mock.Mock()
upstream = peach_http.HttpxTransport()
with mock.patch("peach.follow_stream.IwaraConnector", return_value=SimpleNamespace(transport=owned)):
resolver = FollowMediaResolver(upstream)
resolver.close()
owned.close.assert_called_once_with()
upstream.close()
injected = mock.Mock()
FollowMediaResolver(injected).close()
injected.close.assert_not_called()
def test_iwara_playback_uses_shared_cache_quality_selection_and_no_media_credentials(self):
requests = []
def transport(request, timeout, max_bytes):
requests.append(request)
payload = ({"fileUrl": "https://files.iwara.tv/file/id?expires=2000000000",
"file": {"height": 1080}}
if "/video/" in request.url else [
{"name": "Source", "src": {"view": "//hime.iwara.tv/1080.mp4"}},
{"name": "540", "src": {"view": "//mikoto.iwara.tv/540.mp4"}}])
return HttpResponse(200, {}, json.dumps(payload).encode())
item = SimpleNamespace(id=90, provider="iwara", external_id="Video123",
url="https://www.iwara.tv/video/Video123", metadata={},
media_url="https://www.iwara.tv/video/Video123")
resolver = FollowMediaResolver(transport).with_credential_loader(
lambda _: Credential("iwara", {"access_token": "private-token"}))
self.assertEqual(resolver.resolve(item).url, "https://hime.iwara.tv/1080.mp4")
lower = resolver.resolve(item, height=540)
self.assertEqual(lower.url, "https://mikoto.iwara.tv/540.mp4")
self.assertEqual(len(requests), 2)
self.assertEqual(lower.allowed_hosts, ("iwara.tv",))
self.assertIsNone(lower.headers)
self.assertNotIn("Authorization", requests[1].headers)
self.assertTrue(proxyable("iwara", item.media_url))
def test_iwara_access_failure_is_cached_and_rate_limit_pauses_the_source(self):
transport = mock.Mock(return_value=HttpResponse(429, {"Retry-After": "30"}, b""))
resolver = FollowMediaResolver(transport, clock=lambda: 100)
item = SimpleNamespace(id=90, provider="iwara", external_id="Video123",
url="https://www.iwara.tv/video/Video123", metadata={})
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(item)
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(item)
self.assertEqual(transport.call_count, 1)
self.assertEqual(resolver.cooling("iwara"), 30)
def test_iwara_unknown_original_height_preserves_default_and_explicit_selection(self):
transport = mock.Mock(side_effect=[
HttpResponse(200, {}, json.dumps({
"fileUrl": "https://files.iwara.tv/file/id?expires=2000000000",
"file": {"height": 0}}).encode()),
HttpResponse(200, {}, json.dumps([
{"name": "540", "src": {"view": "//hime.iwara.tv/540.mp4"}},
{"name": "Source", "src": {"view": "//mikoto.iwara.tv/original.mp4"}}]).encode())])
resolver = FollowMediaResolver(transport)
item = SimpleNamespace(id=91, provider="iwara", external_id="Video123",
url="https://www.iwara.tv/video/Video123", metadata={})
original = resolver.resolve(item)
self.assertEqual(original.url, "https://mikoto.iwara.tv/original.mp4")
self.assertEqual(original.qualities[0][0], 0)
self.assertEqual(resolver.resolve(item, height=540).url, "https://hime.iwara.tv/540.mp4")
self.assertEqual(resolver.resolve(item, height=0), original)
self.assertEqual(resolver.resolve(item, height=9999), original)
self.assertEqual(transport.call_count, 2)
def test_rule34video_resolves_the_current_signed_url_and_caches_it(self):
requests = []
def transport(request, timeout, max_bytes):
requests.append(request)
return HttpResponse(200, {}, b"")
item = SimpleNamespace(
id=7, provider="rule34video",
url="https://rule34video.com/video/7/work/",
media_url="https://rule34video.com/get_file/1.mp4/",
metadata={},
)
resolver = FollowMediaResolver(transport)
first = resolver.resolve(item)
second = resolver.resolve(item)
self.assertEqual(first, second)
self.assertEqual(len(requests), 1)
self.assertIn("v-acctoken=fresh", first.url)
self.assertEqual(first.referer, item.url)
def test_non_rule34_media_uses_the_recorded_source_without_a_network_probe(self):
resolver = FollowMediaResolver(lambda *_args: self.fail("unexpected network probe"))
item = SimpleNamespace(
id=8, provider="kemono", url="https://kemono.cr/post/8",
media_url="https://img.kemono.cr/data/8.mp4", metadata={},
)
self.assertEqual(resolver.resolve(item).url, item.media_url)
def test_simpcity_images_on_any_public_image_host_are_proxied_without_a_cookie(self):
"""帖子里的图挂在几十家图站上,白名单追不上;这类媒体不带凭据,放行任意公网主机。"""
resolver = FollowMediaResolver(lambda *_args: self.fail("unexpected network probe"))
item = SimpleNamespace(
id=11, provider="simpcity", url="https://simpcity.cr/threads/17401/post-1",
media_url="https://jpg5.su/img/abc.jpg", metadata={},
)
resolved = resolver.resolve(item)
self.assertEqual(resolved.url, item.media_url)
self.assertIsNone(resolved.headers)
self.assertEqual(resolved.allowed_hosts, ())
self.assertTrue(resolved.public_hosts)
for url in ("https://simp6.cuckcapital.cr/images4/b73b.png", "https://jpg5.su/img/abc.jpg",
"https://i.ibb.co/x/y.png", "https://images2.imgbox.com/ab/cd/ef.jpg"):
self.assertTrue(proxyable("simpcity", url), url)
# 放宽的是域名清单,不是安全边界:明文、IP 字面量、本机与局域网名字、带用户
# 信息的地址照旧拒收。
for url in ("http://jpg5.su/img/abc.jpg", "https://169.254.1.1/a.png", "https://[::1]/a.png",
"https://localhost/a.png", "https://peach.local/a.png", "https://router.lan/a.png",
"https://intranet/a.png", "https://user:pw@jpg5.su/a.png"):
self.assertFalse(proxyable("simpcity", url), url)
# 别的来源仍只认自己的白名单。
self.assertFalse(proxyable("kemono", "https://jpg5.su/img/abc.jpg"))
def test_public_host_media_never_carries_credentials(self):
"""公网模式成立的前提是这条媒体不带凭据,结构上就不允许两者同时出现。"""
with self.assertRaises(ValueError):
ResolvedFollowMedia("https://jpg5.su/a.jpg", headers={"Cookie": "x"},
public_hosts=True)
def test_untrusted_or_credentialed_targets_are_rejected(self):
resolver = FollowMediaResolver(lambda *_args: self.fail("unexpected network probe"))
bad = SimpleNamespace(
id=9, provider="kemono", url="https://kemono.cr/post/9",
media_url="https://127.0.0.1/private.mp4", metadata={},
)
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(bad)
locked = SimpleNamespace(
id=10, provider="f95zone", url="https://f95zone.to/post/10",
media_url="https://f95zone.to/file.mp4",
metadata={"media_needs_credential": True},
)
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(locked)
def test_fanbox_gallery_selects_an_image_by_index(self):
item = SimpleNamespace(
id=11, provider="fanbox", url="https://creator.fanbox.cc/posts/11",
media_url=None, metadata={"media_items": [
{"url": "https://downloads.fanbox.cc/one.jpg",
"resource_provider": "fanbox"},
{"url": "https://downloads.fanbox.cc/two.jpg",
"resource_provider": "fanbox"},
]},
)
target = FollowMediaResolver(lambda *_args: None).resolve(item, 1)
self.assertEqual(target.url, "https://downloads.fanbox.cc/two.jpg")
def test_archive_attachment_list_resolves_only_on_the_items_own_site(self):
"""归档站帖子的附件清单按条目自己站点的主机白名单取,别的主机、别的来源一律拒收。"""
item = SimpleNamespace(
id=15, provider="pawchive", url="https://pawchive.pw/patreon/user/1/post/15",
media_url="https://file.pawchive.pw/data/a/clip.mp4", metadata={"media_items": [
{"url": "https://file.pawchive.pw/data/a/clip.mp4", "resource_provider": "pawchive"},
{"url": "https://file.pawchive.pw/data/a/page2.jpg", "resource_provider": "pawchive"},
{"url": "https://127.0.0.1/data/a/private.jpg", "resource_provider": "pawchive"},
{"url": "https://kemono.cr/data/a/page3.jpg", "resource_provider": "kemono"},
]},
)
resolver = FollowMediaResolver(lambda *_args: None)
target = resolver.resolve(item, 1)
self.assertEqual(target.url, "https://file.pawchive.pw/data/a/page2.jpg")
self.assertEqual(target.referer, item.url)
for index in (2, 3):
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(item, index)
def test_legacy_f95_attachment_is_resolved_as_an_image(self):
item = SimpleNamespace(
id=13, provider="f95zone", url="https://f95zone.to/threads/1/post-13",
media_url="https://pixeldrain.com/l/files", metadata={"attachments": [
"https://attachments.f95zone.to/2026/08/one.jpg",
]},
)
target = FollowMediaResolver(lambda *_args: None).resolve(item, 0)
self.assertEqual(target.url,
"https://attachments.f95zone.to/2026/08/one.jpg")
self.assertEqual(target.referer, item.url)
def test_f95_attachment_stays_available_while_other_links_need_a_session(self):
item = SimpleNamespace(
id=14, provider="f95zone", url="https://f95zone.to/threads/1/post-14",
media_url="https://f95zone.to/masked/gofile.io/1/locked",
metadata={"media_needs_credential": True, "attachments": [
"https://attachments.f95zone.to/2026/08/one.jpg",
]},
)
resolver = FollowMediaResolver(lambda *_args: None).with_credential_loader(
lambda provider: Credential(provider, {"cookie": "xf_session=saved"}))
target = resolver.resolve(item, 0)
self.assertEqual(target.url,
"https://attachments.f95zone.to/2026/08/one.jpg")
self.assertEqual(target.headers, {"Cookie": "xf_session=saved"})
def test_gofile_media_keeps_the_token_in_the_proxy_header(self):
item = SimpleNamespace(
id=12, provider="fanbox", url="https://creator.fanbox.cc/posts/12",
media_url=None, metadata={"media_items": [
{"url": "https://store1.gofile.io/download/one.mp4",
"resource_provider": "gofile"},
]},
)
resolver = FollowMediaResolver(lambda *_args: None).with_credential_loader(
lambda provider: Credential(provider, {"api_token": "secret"}))
target = resolver.resolve(item, 0)
self.assertEqual(target.headers, {"Authorization": "Bearer secret"})
self.assertNotIn("secret", target.url)
def test_the_resolution_cache_stays_bounded(self):
"""只有 rule34video 会进这个缓存,一次列表几百条;只按 TTL 过期的话,
进程活多久它就长多久。"""
def transport(request, timeout, max_bytes):
return HttpResponse(200, {}, b"")
resolver = FollowMediaResolver(transport, max_cache_entries=2)
for identifier in range(6):
resolver.resolve(SimpleNamespace(
id=identifier, provider="rule34video",
url=f"https://rule34video.com/video/{identifier}/x/",
media_url=None, metadata={}))
self.assertEqual(len(resolver._cache), 2)
class Rule34VideoUpstreamDedupeTests(unittest.TestCase):
"""播放器起播时同时问正片和清晰度,之后每个 Range 再问一遍;这些都只该换来一次详情页。"""
PAGE = b""
@staticmethod
def _item(identifier=7):
return SimpleNamespace(id=identifier, provider="rule34video",
url=f"https://rule34video.com/video/{identifier}/x/",
media_url=None, metadata={})
def test_concurrent_requests_for_one_item_share_a_single_detail_fetch(self):
requests = []
entered = threading.Event()
release = threading.Event()
def transport(request, timeout, max_bytes):
requests.append(request.url)
entered.set()
release.wait(5)
return HttpResponse(200, {}, self.PAGE)
resolver = FollowMediaResolver(transport)
results = []
workers = [threading.Thread(target=lambda: results.append(resolver.resolve(self._item())))
for _ in range(3)]
workers[0].start()
self.assertTrue(entered.wait(5))
for worker in workers[1:]:
worker.start()
release.set()
for worker in workers:
worker.join(5)
self.assertEqual(len(requests), 1)
self.assertEqual(len({result.url for result in results}), 1)
self.assertEqual(len(results), 3)
def test_a_missing_detail_page_is_not_refetched_within_the_ttl(self):
clock = [1000.0]
transport = mock.Mock(return_value=HttpResponse(404, {}, b""))
resolver = FollowMediaResolver(transport, clock=lambda: clock[0])
for _ in range(3):
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item())
self.assertEqual(transport.call_count, 1)
clock[0] += resolver.MISS_TTL + 1
transport.return_value = HttpResponse(200, {}, self.PAGE)
self.assertIn("1_720p", resolver.resolve(self._item()).url)
self.assertEqual(transport.call_count, 2)
def test_a_rate_limited_source_pauses_every_item_until_retry_after(self):
clock = [1000.0]
transport = mock.Mock(return_value=HttpResponse(429, {"retry-after": "90"}, b""))
resolver = FollowMediaResolver(transport, clock=lambda: clock[0])
with self.assertLogs("peach.follow_stream", "WARNING"):
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(7))
with self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(8))
self.assertEqual(transport.call_count, 1)
self.assertGreater(resolver.cooling("rule34video"), 0)
clock[0] += 91
self.assertEqual(resolver.cooling("rule34video"), 0)
transport.return_value = HttpResponse(200, {}, self.PAGE)
self.assertIn("1_720p", resolver.resolve(self._item(8)).url)
self.assertEqual(transport.call_count, 2)
def test_retry_after_is_capped_and_missing_retry_after_uses_the_default(self):
clock = [0.0]
resolver = FollowMediaResolver(
mock.Mock(return_value=HttpResponse(403, {"Retry-After": "999999"}, b"")),
clock=lambda: clock[0])
with self.assertLogs("peach.follow_stream", "WARNING"), self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(7))
self.assertEqual(resolver.cooling("rule34video"), resolver.MAX_COOLDOWN)
resolver = FollowMediaResolver(mock.Mock(return_value=HttpResponse(429, {}, b"")),
clock=lambda: clock[0])
with self.assertLogs("peach.follow_stream", "WARNING"), self.assertRaises(FollowMediaUnavailable):
resolver.resolve(self._item(7))
self.assertEqual(resolver.cooling("rule34video"), resolver.COOLDOWN)
class Rule34VideoQualityTests(unittest.TestCase):
"""rule34video 把每档清晰度写成独立字段,只解析 video_url 会永远播最低那档。
2026-08-31 实测 video/4564733 的页面给出四档:
video_url -> 4564733_360.mp4
video_alt_url -> 4564733_480p.mp4
video_alt_url2 -> 4564733_720p.mp4
video_alt_url3 -> 4564733_1080p.mp4
"""
PAGE = (b"")
def _resolver(self, body=None):
def transport(request, timeout, max_bytes):
return HttpResponse(200, {}, self.PAGE if body is None else body)
return FollowMediaResolver(transport)
def _item(self):
return SimpleNamespace(
id=11, provider="rule34video",
url="https://rule34video.com/video/11/x/",
media_url="https://rule34video.com/get_file/1.mp4/",
metadata={},
)
def test_every_quality_is_parsed_and_the_highest_plays_by_default(self):
resolved = self._resolver().resolve(self._item())
self.assertEqual([height for height, _ in resolved.qualities],
[1080, 720, 480, 360])
self.assertIn("1_1080p.mp4", resolved.url,
"默认应当是最高一档,旧实现固定播 360")
def test_a_requested_height_is_honoured(self):
resolved = self._resolver().resolve(self._item(), None, 480)
self.assertIn("1_480p.mp4", resolved.url)
def test_an_unavailable_height_falls_back_instead_of_failing(self):
"""签名 URL 会过期、来源也会改版;选档失败不该中断播放。"""
resolved = self._resolver().resolve(self._item(), None, 4320)
self.assertIn("1_1080p.mp4", resolved.url)
def test_other_providers_report_no_quality_choices(self):
item = SimpleNamespace(
id=12, provider="pawchive",
url="https://pawchive.pw/x",
media_url="https://pawchive.pw/data/a/b/c.mp4",
metadata={},
)
resolved = self._resolver(body=b"").resolve(item)
self.assertEqual(resolved.qualities, (),
"只有 rule34video 给多档,其余来源只显示原画")
class ProxyUpstreamTests(unittest.TestCase):
"""`/follow-stream` 的代理边界:跟哪些跳、转发哪些头、什么时候干脆不转发。
生产用的那个 client 开着 `follow_redirects=True`,所以这里的替身也开着:
要证明的正是「按跳自己判」而不是「靠 client 的默认值」。
"""
def _client(self, handler):
client = httpx.Client(transport=httpx.MockTransport(handler),
follow_redirects=True)
self.addCleanup(client.close)
return client
def test_a_redirect_out_of_the_whitelist_never_carries_the_credential(self):
"""上游只要回一个指向别处的 Location,httpx 就会把 Cookie 一路带过去。"""
seen = []
def handler(request):
seen.append(request)
return httpx.Response(302, request=request, headers={
"location": "https://collector.example/steal.jpg"})
target = ResolvedFollowMedia(
"https://attachments.f95zone.to/2026/08/one.jpg",
"https://f95zone.to/threads/1/post-1",
{"Cookie": "xf_session=fixture"},
allowed_hosts=("attachments.f95zone.to",))
with self.assertRaises(FollowProxyError):
open_upstream(self._client(handler), "GET", target, incoming={})
self.assertEqual([str(request.url) for request in seen],
["https://attachments.f95zone.to/2026/08/one.jpg"],
"出界的那一跳一次都不能发出去")
self.assertEqual(seen[0].headers["cookie"], "xf_session=fixture",
"第一跳确实带着会话,所以拦住的是真的泄露")
def test_a_redirect_inside_the_source_domain_is_still_followed(self):
"""归档站主域会 302 到取文件的节点(实测 kemono → n1.),必须跟得上。"""
def handler(request):
if request.url.host == "kemono.cr":
return httpx.Response(302, request=request, headers={
"location": "https://n1.kemono.cr/data/7.mp4"})
return httpx.Response(200, request=request,
stream=httpx.ByteStream(b"frames"),
headers={"content-type": "video/mp4"})
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4",
allowed_hosts=("kemono.cr",))
upstream = open_upstream(self._client(handler), "GET", target, incoming={})
self.addCleanup(upstream.close)
self.assertEqual(upstream.read(), b"frames")
def test_rule34video_is_followed_out_to_its_cdn(self):
"""rule34video 的 `/get_file/…` 只是签名入口,正片在 `*.boomio-cdn.com`。
这一跳没有凭据可泄露(这条媒体不带 `headers`),拦住它的唯一后果是每条
视频都停在 502。CDN 后缀登记在 `follow_providers` 的 `hosts` 里,代理照样
逐跳校验。
"""
def handler(request):
if request.url.host == "rule34video.com":
return httpx.Response(302, request=request, headers={
"location": "https://eu-cdn05-prem.boomio-cdn.com/x.mp4"})
return httpx.Response(206, request=request,
stream=httpx.ByteStream(b"frames"),
headers={"content-type": "video/mp4"})
target = ResolvedFollowMedia(
"https://rule34video.com/get_file/1/1080p.mp4/?v-acctoken=fresh",
"https://rule34video.com/video/1/work/",
allowed_hosts=("rule34video.com", "boomio-cdn.com"))
upstream = open_upstream(self._client(handler), "GET", target, incoming={})
self.addCleanup(upstream.close)
self.assertEqual(upstream.read(), b"frames")
def test_an_upstream_error_page_is_refused_instead_of_forwarded(self):
"""403 页面、限流提示、错误 JSON 转发给播放器没有用处,只会把上游的
主机名和提示语抄给浏览器。"""
def handler(request):
return httpx.Response(403, request=request,
content=b"blocked by upstream shield")
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4",
allowed_hosts=("kemono.cr",))
with self.assertRaises(FollowProxyError) as raised:
open_upstream(self._client(handler), "GET", target, incoming={})
self.assertIn("403", str(raised.exception))
self.assertEqual(raised.exception.status_code, 403)
self.assertNotIn("shield", str(raised.exception))
def test_a_redirect_loop_is_bounded(self):
seen = []
def handler(request):
seen.append(request)
return httpx.Response(302, request=request, headers={
"location": "https://kemono.cr/data/next.mp4"})
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4",
allowed_hosts=("kemono.cr",))
with self.assertRaises(FollowProxyError):
open_upstream(self._client(handler), "GET", target, incoming={})
self.assertEqual(len(seen), MAX_PROXY_REDIRECTS + 1)
def test_media_without_a_whitelist_is_not_proxied_at_all(self):
target = ResolvedFollowMedia("https://kemono.cr/data/7.mp4")
with self.assertRaises(FollowProxyError):
open_upstream(
self._client(lambda request: self.fail("不该发出请求")),
"GET", target, incoming={})
def test_public_host_media_follows_redirects_across_public_hosts(self):
"""图站常把原图 302 到自己的 CDN,公网模式下这一跳要跟得上。"""
def handler(request):
if request.url.host == "jpg5.su":
return httpx.Response(302, request=request, headers={
"location": "https://cdn.jpg5cdn.example/a.jpg"})
return httpx.Response(200, request=request,
stream=httpx.ByteStream(b"pixels"),
headers={"content-type": "image/jpeg"})
target = ResolvedFollowMedia("https://jpg5.su/a.jpg", public_hosts=True)
with mock.patch.object(peach_http, "host_addresses", return_value=("93.184.216.34",)):
upstream = open_upstream(self._client(handler), "GET", target, incoming={})
self.addCleanup(upstream.close)
self.assertEqual(upstream.read(), b"pixels")
self.assertNotIn("cookie", upstream.request.headers)
def test_public_host_media_never_reaches_into_the_lan(self):
"""域名字面上像公网但解析到内网,或者上游把我们指到内网地址:一次都不发。"""
seen = []
def to_lan(request):
seen.append(request)
return httpx.Response(302, request=request, headers={
"location": "https://169.254.169.254/admin.png"})
target = ResolvedFollowMedia("https://jpg5.su/a.jpg", public_hosts=True)
with mock.patch.object(peach_http, "host_addresses", return_value=("93.184.216.34",)):
with self.assertRaises(FollowProxyError):
open_upstream(self._client(to_lan), "GET", target, incoming={})
self.assertEqual([str(request.url) for request in seen], ["https://jpg5.su/a.jpg"])
for addresses in ((), ("127.0.0.1",), ("93.184.216.34", "fd00::1")):
with mock.patch.object(peach_http, "host_addresses", return_value=addresses):
with self.assertRaises(FollowProxyError, msg=str(addresses)):
open_upstream(self._client(lambda request: self.fail("不该发出请求")),
"GET", target, incoming={})
def test_only_range_headers_come_in_and_only_media_headers_go_back(self):
target = ResolvedFollowMedia(
"https://kemono.cr/data/7.mp4", "https://kemono.cr/post/7",
{"Cookie": "source_session=fixture"}, allowed_hosts=("kemono.cr",))
headers = proxy_request_headers(target, {
"range": "bytes=0-1", "accept": "video/*",
# 浏览器发给 Peach 的凭据不能跟着请求送到上游去。
"cookie": "peach=token", "authorization": "Bearer peach",
})
self.assertEqual(headers["Range"], "bytes=0-1")
self.assertEqual(headers["Accept"], "video/*")
self.assertEqual(headers["Referer"], "https://kemono.cr/post/7")
self.assertEqual(headers["Cookie"], "source_session=fixture")
self.assertNotIn("Authorization", headers)