612c417024
gates / gates (push) Successful in 19s
Message bundles (internal/i18n) expanded into templates before parsing, one template set per language. Launcher, /backups, /apps/<slug> and the layout converted; household language setting, POST /settings/language, ?lang= override, report field. Parity test against fixtures captured from unconverted templates; copy gates read templates expanded; new i18n_missing_gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
92 lines
3.2 KiB
Go
92 lines
3.2 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"html/template"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// TestRouteUnpublished asserts F5: routeUnpublished is true exactly for the states where Traefik
|
|
// withholds the public route (unhealthy / restarting) and false otherwise.
|
|
func TestRouteUnpublished(t *testing.T) {
|
|
cases := []struct {
|
|
state stacks.ContainerState
|
|
want bool
|
|
}{
|
|
{stacks.StateUnhealthy, true},
|
|
{stacks.StateRestarting, true},
|
|
{stacks.StateRunning, false},
|
|
{stacks.StateStarting, false},
|
|
{stacks.StateDeploying, false},
|
|
{stacks.StateStopped, false},
|
|
{stacks.StateExited, false},
|
|
{stacks.StateNotDeployed, false},
|
|
{stacks.StatePaused, false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := routeUnpublished(c.state); got != c.want {
|
|
t.Errorf("routeUnpublished(%q) = %v, want %v", c.state, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestTemplatesParseWithFuncmap asserts the real embedded templates (including the stacks.html /
|
|
// dashboard.html edits that reference routeUnpublished) parse with the production funcmap. Catches an
|
|
// unregistered func or a template syntax error introduced by the F5 edits.
|
|
func TestTemplatesParseWithFuncmap(t *testing.T) {
|
|
s := &Server{cfg: &config.Config{}}
|
|
// v0.247.0: templates carry {{T}} markers and are parsed per language after i18n expansion — the
|
|
// production path is parseTemplateSet, so parse exactly that, for every supported language.
|
|
b, err := i18n.Shared()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, lang := range i18n.Supported {
|
|
funcs := s.templateFuncMap()
|
|
for n, fn := range s.localeFuncs(lang) {
|
|
funcs[n] = fn
|
|
}
|
|
if _, _, err := parseTemplateSet(b, lang, funcs); err != nil {
|
|
t.Fatalf("templates (%s) failed to parse with funcmap: %v", lang, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRouteUnpublishedIndicatorRenders asserts the dashboard/stacks card guard renders the distinct
|
|
// indicator for a DEPLOYED + unhealthy stack, and NOT for a healthy one — the exact condition both
|
|
// edited templates use ({{if and .Deployed (routeUnpublished .State)}}).
|
|
func TestRouteUnpublishedIndicatorRenders(t *testing.T) {
|
|
s := &Server{cfg: &config.Config{}}
|
|
const frag = `{{if and .Deployed (routeUnpublished .State)}}URL-NOT-PUBLISHED{{end}}`
|
|
tmpl, err := template.New("frag").Funcs(s.templateFuncMap()).Parse(frag)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
type row struct {
|
|
Deployed bool
|
|
State stacks.ContainerState
|
|
}
|
|
render := func(r row) string {
|
|
var b bytes.Buffer
|
|
if err := tmpl.Execute(&b, r); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
if got := render(row{Deployed: true, State: stacks.StateUnhealthy}); !strings.Contains(got, "URL-NOT-PUBLISHED") {
|
|
t.Errorf("deployed+unhealthy should show the indicator, got %q", got)
|
|
}
|
|
if got := render(row{Deployed: true, State: stacks.StateRunning}); strings.Contains(got, "URL-NOT-PUBLISHED") {
|
|
t.Errorf("deployed+running must NOT show the indicator, got %q", got)
|
|
}
|
|
if got := render(row{Deployed: false, State: stacks.StateUnhealthy}); strings.Contains(got, "URL-NOT-PUBLISHED") {
|
|
t.Errorf("not-deployed must NOT show the indicator, got %q", got)
|
|
}
|
|
}
|