mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Repris d'AJEAN 0.17.5. - task_create accepte in_minutes (dans N minutes) ou at (« HH:MM », prochaine occurrence, ou « AAAA-MM-JJ HH:MM ») pour une tâche unique, calculée côté serveur : le modèle n'a plus à écrire un cron ni à jongler avec les fuseaux pour un simple rappel. Elle s'écrit « @once AAAA-MM-JJ HH:MM » et se désactive après son passage ; manquée pendant un arrêt, elle part au redémarrage. - Le navigateur envoie son fuseau (Europe/Paris…) avec chaque message ; il est retenu et sert aux tâches que l'IA planifie. Le conteneur tourne en UTC : « à 17h » tombait deux heures à côté. La réponse de task_create nomme le fuseau utilisé. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.3 KiB
Go
69 lines
2.3 KiB
Go
package loki
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// « @once » : une date future est acceptée, une date passée refusée.
|
|
func TestScheduleOnce(t *testing.T) {
|
|
from := time.Date(2026, 10, 2, 12, 0, 0, 0, time.UTC)
|
|
at, err := nextAfter("@once 2026-10-02 12:30", "UTC", from)
|
|
if err != nil || !at.Equal(from.Add(30*time.Minute)) {
|
|
t.Fatalf("@once futur : %v, %v", at, err)
|
|
}
|
|
if _, err := nextAfter("@once 2026-10-02 11:00", "UTC", from); err == nil {
|
|
t.Fatal("@once passé accepté")
|
|
}
|
|
if !isOnce(" @ONCE 2026-10-02 12:30") || isOnce("@every 2h") {
|
|
t.Fatal("isOnce se trompe")
|
|
}
|
|
}
|
|
|
|
// L'IA crée un rappel « dans 20 minutes » ou « à HH:MM » sans écrire de cron,
|
|
// dans le fuseau du navigateur ; une fois passée, la tâche se désactive.
|
|
func TestTacheUniqueParLIA(t *testing.T) {
|
|
testHome(t)
|
|
rememberUserTZ("Europe/Paris")
|
|
if got := defaultTaskTZ(); got != "Europe/Paris" {
|
|
t.Fatalf("fuseau retenu = %q", got)
|
|
}
|
|
rememberUserTZ("Pas/Un_Fuseau") // invalide : ignoré
|
|
if got := defaultTaskTZ(); got != "Europe/Paris" {
|
|
t.Fatalf("fuseau invalide retenu : %q", got)
|
|
}
|
|
|
|
out := toolTaskCreate(map[string]any{"name": "rappel", "prompt": "dis bonjour", "in_minutes": float64(20)})
|
|
if !strings.HasPrefix(out, "[ok]") || !strings.Contains(out, "Europe/Paris") {
|
|
t.Fatalf("création « dans 20 min » : %s", out)
|
|
}
|
|
out = toolTaskCreate(map[string]any{"name": "apéro", "prompt": "rappelle l'apéro", "at": "18:30"})
|
|
if !strings.HasPrefix(out, "[ok]") {
|
|
t.Fatalf("création « à 18:30 » : %s", out)
|
|
}
|
|
if out := toolTaskCreate(map[string]any{"name": "flou", "prompt": "x", "at": "demain"}); !strings.HasPrefix(out, "[erreur]") {
|
|
t.Fatalf("'at' invalide accepté : %s", out)
|
|
}
|
|
|
|
var once Task
|
|
for _, tk := range listTasks() {
|
|
if tk.Name == "rappel" {
|
|
once = tk
|
|
}
|
|
}
|
|
if !isOnce(once.Schedule) || once.TZ != "Europe/Paris" || once.NextRun == 0 {
|
|
t.Fatalf("tâche unique mal posée : %+v", once)
|
|
}
|
|
want := time.Now().Add(20 * time.Minute)
|
|
if d := time.UnixMilli(once.NextRun).Sub(want); d > time.Minute || d < -time.Minute {
|
|
t.Fatalf("prochaine exécution à %v, attendu ≈ %v", time.UnixMilli(once.NextRun), want)
|
|
}
|
|
|
|
recordTaskEnd(once.ID, time.Now(), "bonjour", nil)
|
|
after, _ := getTask(once.ID)
|
|
if after.Enabled || after.NextRun != 0 {
|
|
t.Fatalf("tâche unique encore active après son passage : %+v", after)
|
|
}
|
|
}
|