6ed223886d
gates / gates (push) Successful in 46s
Found on demo-hp with 0.325.0-rc.1: the store (and so the conversion of an old family.json) opened only on the first family request; a box nobody visits through a family gate kept the session values. OpenFamilyStore from main, one INFO line with the count. TestR1112_StartConvertsTheFamilyFile, TestR1112_MainOpensTheFamilyStore, red-proved. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
86 lines
2.7 KiB
Go
86 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/crashboot"
|
|
)
|
|
|
|
// R-856 (`09` §3 decision 143): the dead-app check — the source of app_start_failed and
|
|
// app_stopped_unhealthy — waits on the crash-aware boot grace, not on the fixed 90 s constant.
|
|
//
|
|
// Red-proof: put `time.Since(startTime) < deadAppBootGrace` back as the check's gate — this test fails.
|
|
func TestR856_DeadAppCheckWaitsOnTheCrashAwareGrace(t *testing.T) {
|
|
f, err := parser.ParseFile(token.NewFileSet(), "main.go", nil, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var body *ast.BlockStmt
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
call, ok := n.(*ast.CallExpr)
|
|
if !ok || len(call.Args) < 3 {
|
|
return true
|
|
}
|
|
if sel, ok := call.Fun.(*ast.SelectorExpr); !ok || sel.Sel.Name != "Every" {
|
|
return true
|
|
}
|
|
if lit, ok := call.Args[0].(*ast.BasicLit); ok && lit.Value == `"deadapp-check"` {
|
|
if fl, ok := call.Args[2].(*ast.FuncLit); ok {
|
|
body = fl.Body
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
if body == nil {
|
|
t.Fatal("the deadapp-check schedule is gone from main.go")
|
|
}
|
|
within, constant := false, false
|
|
ast.Inspect(body, func(n ast.Node) bool {
|
|
switch x := n.(type) {
|
|
case *ast.SelectorExpr:
|
|
if id, ok := x.X.(*ast.Ident); ok && id.Name == "bootGrace" && x.Sel.Name == "Within" {
|
|
within = true
|
|
}
|
|
case *ast.Ident:
|
|
if x.Name == "deadAppBootGrace" {
|
|
constant = true
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
if !within || constant {
|
|
t.Errorf("the dead-app check must gate on bootGrace.Within (found=%v) and not on deadAppBootGrace (found=%v)", within, constant)
|
|
}
|
|
}
|
|
|
|
// The normal grace is today's 90 s: a normal boot must keep it exactly (decision 143's other half).
|
|
func TestR856_NormalGraceIsTheDeadAppBootGrace(t *testing.T) {
|
|
if crashboot.NormalGrace != deadAppBootGrace {
|
|
t.Errorf("crashboot.NormalGrace = %s, deadAppBootGrace = %s — a normal boot must keep today's grace", crashboot.NormalGrace, deadAppBootGrace)
|
|
}
|
|
}
|
|
|
|
// R-1112: main opens the family store at start (webServer.OpenFamilyStore), so an old family.json is converted on every
|
|
// box — not only on one where someone passes a family gate. Red-proof: remove the call from main.go → this fails.
|
|
func TestR1112_MainOpensTheFamilyStore(t *testing.T) {
|
|
f, err := parser.ParseFile(token.NewFileSet(), "main.go", nil, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
if call, ok := n.(*ast.CallExpr); ok {
|
|
if sel, ok := call.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "OpenFamilyStore" {
|
|
found = true
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
if !found {
|
|
t.Fatal("main.go does not call OpenFamilyStore — an old family.json keeps its session values until a family request")
|
|
}
|
|
}
|