fix: 收紧 WebGUI 默认安全配置
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import unittest
|
||||
from io import BytesIO
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from server import ApiError, Handler, MAX_BODY_BYTES, compute_next_run_at
|
||||
|
||||
|
||||
class ComputeNextRunAtTest(unittest.TestCase):
|
||||
def test_daily_uses_same_day_when_time_is_future(self):
|
||||
after = datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc)
|
||||
self.assertEqual(
|
||||
compute_next_run_at("daily", "23:30", 0, after),
|
||||
"2026-06-20T23:30:00Z",
|
||||
)
|
||||
|
||||
def test_daily_moves_to_next_day_when_time_has_passed(self):
|
||||
after = datetime(2026, 6, 20, 23, 30, tzinfo=timezone.utc)
|
||||
self.assertEqual(
|
||||
compute_next_run_at("daily", "23:30", 0, after),
|
||||
"2026-06-21T23:30:00Z",
|
||||
)
|
||||
|
||||
def test_weekly_uses_next_target_weekday(self):
|
||||
after = datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc) # Saturday
|
||||
self.assertEqual(
|
||||
compute_next_run_at("weekly", "09:00", 0, after, weekday=1),
|
||||
"2026-06-22T09:00:00Z",
|
||||
)
|
||||
|
||||
def test_weekly_rolls_forward_a_week_after_same_day_time_passed(self):
|
||||
after = datetime(2026, 6, 22, 10, 0, tzinfo=timezone.utc) # Monday
|
||||
self.assertEqual(
|
||||
compute_next_run_at("weekly", "09:00", 0, after, weekday=1),
|
||||
"2026-06-29T09:00:00Z",
|
||||
)
|
||||
|
||||
def test_monthly_uses_next_valid_monthday(self):
|
||||
after = datetime(2026, 1, 30, 12, 0, tzinfo=timezone.utc)
|
||||
self.assertEqual(
|
||||
compute_next_run_at("monthly", "23:00", 0, after, monthday=31),
|
||||
"2026-01-31T23:00:00Z",
|
||||
)
|
||||
|
||||
def test_monthly_skips_short_months_for_day_31(self):
|
||||
after = datetime(2026, 2, 1, 0, 0, tzinfo=timezone.utc)
|
||||
self.assertEqual(
|
||||
compute_next_run_at("monthly", "23:00", 0, after, monthday=31),
|
||||
"2026-03-31T23:00:00Z",
|
||||
)
|
||||
|
||||
def test_timezone_offset_is_applied_from_local_schedule_time(self):
|
||||
after = datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc)
|
||||
self.assertEqual(
|
||||
compute_next_run_at("daily", "23:00", 8 * 60, after),
|
||||
"2026-06-20T15:00:00Z",
|
||||
)
|
||||
|
||||
def test_invalid_monthday_raises(self):
|
||||
after = datetime(2026, 6, 20, 12, 0, tzinfo=timezone.utc)
|
||||
with self.assertRaises(ValueError):
|
||||
compute_next_run_at("monthly", "23:00", 0, after, monthday=32)
|
||||
|
||||
|
||||
class HandlerSecurityTest(unittest.TestCase):
|
||||
def test_allowed_origin_is_accepted(self):
|
||||
handler = type("DummyHandler", (), {"headers": {"Origin": "http://localhost:5580"}})()
|
||||
Handler.require_allowed_origin(handler)
|
||||
|
||||
def test_disallowed_origin_is_rejected(self):
|
||||
handler = type("DummyHandler", (), {"headers": {"Origin": "http://example.invalid"}})()
|
||||
with self.assertRaises(ApiError) as ctx:
|
||||
Handler.require_allowed_origin(handler)
|
||||
self.assertEqual(ctx.exception.status, 403)
|
||||
|
||||
def test_request_body_size_is_limited(self):
|
||||
handler = type(
|
||||
"DummyHandler",
|
||||
(),
|
||||
{
|
||||
"headers": {"Content-Length": str(MAX_BODY_BYTES + 1)},
|
||||
"rfile": BytesIO(),
|
||||
},
|
||||
)()
|
||||
with self.assertRaises(ApiError) as ctx:
|
||||
Handler.read_json(handler)
|
||||
self.assertEqual(ctx.exception.status, 413)
|
||||
|
||||
def test_read_json_accepts_small_object(self):
|
||||
body = b'{"ok":true}'
|
||||
handler = type(
|
||||
"DummyHandler",
|
||||
(),
|
||||
{
|
||||
"headers": {"Content-Length": str(len(body))},
|
||||
"rfile": BytesIO(body),
|
||||
},
|
||||
)()
|
||||
self.assertEqual(Handler.read_json(handler), {"ok": True})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user