first commit
This commit is contained in:
62
tests/frontend_modules.test.mjs
Normal file
62
tests/frontend_modules.test.mjs
Normal file
@@ -0,0 +1,62 @@
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
global.window = {PYTORRENT_DISABLE_AUTOSTART: true};
|
||||
const app = await import('../pytorrent/static/js/app.js');
|
||||
const source = app.buildRuntimeSource();
|
||||
|
||||
assert.equal(app.moduleSources.length, 12, 'all frontend module chunks are loaded');
|
||||
assert.doesNotThrow(() => Function('io', source), 'assembled frontend runtime compiles');
|
||||
|
||||
for (const marker of [
|
||||
'function renderRow',
|
||||
'function renderTable',
|
||||
'function scheduleRender',
|
||||
'async function post',
|
||||
'async function loadRss',
|
||||
'async function loadSmartQueue',
|
||||
'function ensurePlannerToolsUI',
|
||||
'function loadPlannerPreview',
|
||||
'function pollerPayload',
|
||||
'function pollerDiagnostics',
|
||||
'function renderHealthDashboard',
|
||||
'function recordNotification',
|
||||
'function drawTrafficHistory',
|
||||
"socket.on('connect'",
|
||||
'/api/download-planner/preview',
|
||||
'plannerProfileName',
|
||||
'pollerTorrentList',
|
||||
]) {
|
||||
assert.ok(source.includes(marker), `runtime contains ${marker}`);
|
||||
}
|
||||
|
||||
function extractFunction(src, name){
|
||||
const start = src.indexOf(`function ${name}`);
|
||||
assert.ok(start >= 0, `found function ${name}`);
|
||||
const open = src.indexOf('{', start);
|
||||
let depth = 0;
|
||||
for(let i=open; i<src.length; i++){
|
||||
const ch = src[i];
|
||||
if(ch === '{') depth++;
|
||||
if(ch === '}') depth--;
|
||||
if(depth === 0) return src.slice(start, i + 1);
|
||||
}
|
||||
throw new Error(`unterminated function ${name}`);
|
||||
}
|
||||
|
||||
const escLine = source.match(/const esc = .*?;\n/)?.[0];
|
||||
assert.ok(escLine, 'found esc helper');
|
||||
|
||||
const renderHarness = new Function(`${escLine}
|
||||
${extractFunction(source, 'progressBar')}
|
||||
${extractFunction(source, 'compactCell')}
|
||||
${extractFunction(source, 'table')}
|
||||
${extractFunction(source, 'smartQueueToastMessage')}
|
||||
return {esc, progressBar, compactCell, table, smartQueueToastMessage};`)();
|
||||
|
||||
assert.equal(renderHarness.esc('<tag>&"'), '<tag>&"', 'esc escapes HTML');
|
||||
assert.ok(renderHarness.progressBar(42).includes('42%'), 'progressBar renders percentage');
|
||||
assert.ok(renderHarness.compactCell('x'.repeat(200)).includes('title='), 'compactCell renders title for long text');
|
||||
assert.ok(renderHarness.table(['A'], [['B']]).includes('<table'), 'table renders HTML table');
|
||||
assert.ok(renderHarness.smartQueueToastMessage({stopped:[1,2], started:[3], max_active_downloads:5}).includes('Smart Queue:'), 'smartQueue toast renders');
|
||||
|
||||
console.log('frontend module tests passed');
|
||||
106
tests/planner_poller_services_smoke.py
Normal file
106
tests/planner_poller_services_smoke.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from pytorrent.services import download_planner, poller_control
|
||||
|
||||
|
||||
def test_planner_evaluate_network_caps():
|
||||
download_planner._override_until = lambda profile_id: ""
|
||||
settings = download_planner.normalize({
|
||||
"enabled": True,
|
||||
"profile_name": "low power mode",
|
||||
"network_protection_enabled": True,
|
||||
"network_max_down": 100,
|
||||
"network_max_up": 50,
|
||||
"weekday_down": 1000,
|
||||
"weekday_up": 500,
|
||||
})
|
||||
decision = download_planner.evaluate({"id": 1}, settings, datetime(2026, 5, 13, 12, 0))
|
||||
assert decision["profile_name"] == "low power mode"
|
||||
assert decision["down"] == 100
|
||||
assert decision["up"] == 50
|
||||
assert "network_limit_down" in decision["reasons"]
|
||||
|
||||
|
||||
def test_poller_metrics_and_fallback():
|
||||
settings = poller_control.normalize_settings({
|
||||
"active_interval_seconds": -1,
|
||||
"safe_fallback_enabled": True,
|
||||
"slow_response_threshold_ms": 200,
|
||||
})
|
||||
assert settings["active_interval_seconds"] > 0
|
||||
state = poller_control.ProfilePollState(profile_id=1)
|
||||
runtime = poller_control.mark_tick(
|
||||
state,
|
||||
time.monotonic() - 0.5,
|
||||
active=True,
|
||||
ok=True,
|
||||
emitted_payload_size=1234,
|
||||
rtorrent_call_count=2,
|
||||
skipped_emissions=1,
|
||||
settings=settings,
|
||||
)
|
||||
assert runtime["emitted_payload_size"] == 1234
|
||||
assert runtime["rtorrent_call_count"] == 2
|
||||
assert runtime["adaptive_mode"] in {"normal", "idle", "slowdown", "recovery"}
|
||||
|
||||
fixed_state = poller_control.ProfilePollState(profile_id=2, adaptive_mode="slowdown", slow_count=5)
|
||||
fixed_runtime = poller_control.mark_tick(
|
||||
fixed_state,
|
||||
time.monotonic() - 1.0,
|
||||
active=True,
|
||||
ok=True,
|
||||
settings={**settings, "adaptive_enabled": False},
|
||||
)
|
||||
assert fixed_runtime["adaptive_enabled"] is False
|
||||
assert fixed_runtime["adaptive_mode"] == "fixed"
|
||||
assert fixed_runtime["slow_count"] == 0
|
||||
|
||||
|
||||
def test_poller_background_slow_task_state():
|
||||
state = poller_control.ProfilePollState(profile_id=3)
|
||||
assert state.slow_task_running is False
|
||||
state.slow_task_running = True
|
||||
runtime = poller_control.mark_tick(
|
||||
state,
|
||||
time.monotonic() - 0.05,
|
||||
active=True,
|
||||
ok=True,
|
||||
settings={"adaptive_enabled": False, "slow_response_threshold_ms": 200},
|
||||
skipped_emissions=1,
|
||||
)
|
||||
assert runtime["adaptive_mode"] == "fixed"
|
||||
assert runtime["skipped_emissions"] >= 1
|
||||
assert state.slow_task_running is True
|
||||
|
||||
|
||||
def test_poller_requested_fast_defaults():
|
||||
settings = poller_control.normalize_settings({})
|
||||
assert settings["active_interval_seconds"] == 0.5
|
||||
assert settings["torrent_list_interval_seconds"] == 0.5
|
||||
assert settings["idle_interval_seconds"] == 3.0
|
||||
assert settings["error_interval_seconds"] == 2.0
|
||||
assert settings["system_stats_interval_seconds"] == 1.0
|
||||
assert settings["tracker_stats_interval_seconds"] == 30.0
|
||||
assert settings["disk_stats_interval_seconds"] == 30.0
|
||||
assert settings["queue_stats_interval_seconds"] == 5.0
|
||||
assert settings["heartbeat_interval_seconds"] == 5.0
|
||||
assert settings["slow_response_threshold_ms"] == 10000.0
|
||||
assert settings["slowdown_multiplier"] == 1.0
|
||||
state = poller_control.ProfilePollState(profile_id=4)
|
||||
runtime = poller_control.mark_tick(state, time.monotonic() - 0.01, active=True, ok=True, settings=settings)
|
||||
assert runtime["effective_interval_seconds"] == 0.5
|
||||
assert runtime["configured_min_interval_seconds"] == 0.5
|
||||
assert "last_tick_gap_ms" in runtime
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_planner_evaluate_network_caps()
|
||||
test_poller_metrics_and_fallback()
|
||||
test_poller_background_slow_task_state()
|
||||
test_poller_requested_fast_defaults()
|
||||
print("planner/poller service smoke tests passed")
|
||||
Reference in New Issue
Block a user