feat(web): pages locales aide / statistiques / réglages (serveur HTTP 127.0.0.1)
Nouveau module webserver.nim : mini-serveur HTTP local (port libre a partir de 38943) qui sert des pages HTML generes en Nim - aide (nouvelle entree de menu tray), statistiques et reglages. La page reglages enregistre via POST (config.json) et l'application recharge la configuration au tick suivant. Menu Aide/Statistiques/Reglages ouvrent le navigateur par defaut, avec repli sur les dialogues natifs si le serveur est indisponible. Refactor config/stats (loadConfigFrom/saveConfigTo, loadStatsFrom/saveStatsTo) pour tester sans toucher aux fichiers reels. 8 suites de tests (60 tests), ajout de test_webserver avec integration HTTP reelle.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
## test_webserver.nim
|
||||
## ==================
|
||||
## Tests du module webserver : parsing HTTP, décodage du formulaire,
|
||||
## application des réglages, rendu des pages et intégration réelle
|
||||
## (serveur local 127.0.0.1 interrogé en HTTP).
|
||||
|
||||
import std/[unittest, httpclient, os, uri, tables, strutils]
|
||||
import ../src/webserver
|
||||
import ../src/config
|
||||
|
||||
proc tmpDir(): string =
|
||||
getAppFilename().parentDir / ".webdata_test"
|
||||
|
||||
suite "http helpers":
|
||||
test "parseRequest parses GET with query":
|
||||
let req = parseRequest("GET /help?lang=fr HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n")
|
||||
check req.verb == "GET"
|
||||
check req.path == "/help"
|
||||
check req.body == ""
|
||||
|
||||
test "parseRequest parses POST with body":
|
||||
let req = parseRequest(
|
||||
"POST /settings HTTP/1.1\r\nContent-Length: 10\r\n\r\nworkMin=30")
|
||||
check req.verb == "POST"
|
||||
check req.path == "/settings"
|
||||
check req.body == "workMin=30"
|
||||
|
||||
test "parseRequest decodes percent-encoding":
|
||||
let req = parseRequest("GET /settings%3Fx HTTP/1.1\r\n\r\n")
|
||||
check req.path == "/settings?x"
|
||||
|
||||
test "htmlEscape escapes special characters":
|
||||
check htmlEscape("<a href=\"x\">&'") == "<a href="x">&'"
|
||||
|
||||
test "parseForm decodes urlencoded pairs":
|
||||
let f = parseForm("workMin=30&lang=fr&soundEnabled=on¬e=Hello%20world")
|
||||
check f["workMin"] == "30"
|
||||
check f["lang"] == "fr"
|
||||
check f["soundEnabled"] == "on"
|
||||
check f["note"] == "Hello world"
|
||||
|
||||
suite "applySettings":
|
||||
test "applies a full form":
|
||||
let base = defaultConfig()
|
||||
let form = parseForm("workMin=40&shortBreakMin=8&longBreakMin=20&" &
|
||||
"cyclesBeforeLongBreak=5&dailyGoal=6&lang=en&activeProfile=Sprint&" &
|
||||
"soundEnabled=on&doNotDisturb=on&" &
|
||||
"reminders_json=" & encodeUrl("""[{"text":"Boire de l'eau","enabled":true}]"""))
|
||||
let cfg = applySettings(base, form)
|
||||
check cfg.workMin == 40
|
||||
check cfg.shortBreakMin == 8
|
||||
check cfg.longBreakMin == 20
|
||||
check cfg.cyclesBeforeLongBreak == 5
|
||||
check cfg.dailyGoal == 6
|
||||
check cfg.lang == "en"
|
||||
check cfg.activeProfile == "Sprint"
|
||||
check cfg.soundEnabled
|
||||
check cfg.doNotDisturb
|
||||
check not cfg.autoStart
|
||||
check cfg.healthReminders.len == 1
|
||||
check cfg.healthReminders[0].text == "Boire de l'eau"
|
||||
|
||||
test "invalid values fall back to current ones":
|
||||
let base = defaultConfig()
|
||||
let form = parseForm("workMin=abc&lang=zz&activeProfile=Inexistant")
|
||||
let cfg = applySettings(base, form)
|
||||
check cfg.workMin == base.workMin
|
||||
check cfg.lang == "auto"
|
||||
check cfg.activeProfile == base.activeProfile
|
||||
|
||||
suite "rendering":
|
||||
test "pages contain expected content":
|
||||
var cfg = defaultConfig()
|
||||
cfg.lang = "fr" # langue fixe : le rendu ne dépend pas du système
|
||||
check "NimPulse" in renderHelp(cfg)
|
||||
check "Pomodoro" in renderHelp(cfg)
|
||||
check "Statistiques" in renderStats(cfg)
|
||||
check "Réglages" in renderSettings(cfg)
|
||||
check "name=\"workMin\"" in renderSettings(cfg)
|
||||
check "reminders_json" in renderSettings(cfg)
|
||||
|
||||
suite "integration (local HTTP server)":
|
||||
test "GET routes work and settings POST persists":
|
||||
let dir = tmpDir()
|
||||
removeDir(dir)
|
||||
createDir(dir)
|
||||
# Configuration initiale en français : le rendu est déterministe.
|
||||
var initCfg = defaultConfig()
|
||||
initCfg.lang = "fr"
|
||||
saveConfigTo(initCfg, dir / "config.json")
|
||||
setDataDirOverride(dir)
|
||||
defer: setDataDirOverride("")
|
||||
|
||||
let port = startWebServer()
|
||||
check port > 0
|
||||
defer: stopWebServer()
|
||||
|
||||
let base = "http://127.0.0.1:" & $port
|
||||
let client = newHttpClient()
|
||||
|
||||
let help = client.getContent(base & "/help")
|
||||
check "NimPulse" in help
|
||||
|
||||
let stats = client.getContent(base & "/stats")
|
||||
check "Statistiques" in stats
|
||||
|
||||
let settings = client.getContent(base & "/settings")
|
||||
check "name=\"workMin\"" in settings
|
||||
|
||||
# Sauvegarde via POST (comme le ferait le navigateur).
|
||||
let body = "workMin=33&shortBreakMin=6&longBreakMin=18&cyclesBeforeLongBreak=4&" &
|
||||
"dailyGoal=5&lang=fr&activeProfile=Classique&" &
|
||||
"reminders_json=" & encodeUrl(
|
||||
"""[{"text":"Étire-toi","enabled":true},{"text":"Bois de l'eau","enabled":false}]""")
|
||||
let resp = client.postContent(base & "/settings", body)
|
||||
check "Réglages enregistrés" in resp
|
||||
|
||||
# Le fichier config.json a été écrit dans le répertoire de test.
|
||||
let saved = loadConfigFrom(dir / "config.json")
|
||||
check saved.workMin == 33
|
||||
check saved.dailyGoal == 5
|
||||
check saved.lang == "fr"
|
||||
check saved.healthReminders.len == 2
|
||||
check saved.healthReminders[1].enabled == false
|
||||
|
||||
# Le drapeau de rechargement est posé puis consommé.
|
||||
check consumeSettingsChanged()
|
||||
check not consumeSettingsChanged()
|
||||
|
||||
# La page réglages reflète les nouvelles valeurs.
|
||||
let settings2 = client.getContent(base & "/settings")
|
||||
check "value=\"33\"" in settings2
|
||||
|
||||
removeDir(dir)
|
||||
Reference in New Issue
Block a user