Files
felhom-controller/controller/internal/web/r691_kept_view_test.go
T
admin b6810f14ff
gates / gates (push) Successful in 23s
v0.275.0: a backup's data and its version travel together (R-696, 07 §6.6, D4 option A); R-695, R-691, R-694
The unit's data files are stamped with the versions that wrote them; the capture keeps the
definition the data belongs to; a restore never starts data under another version's
definition (unit restores refuse a mismatch; the off-site restore writes the snapshot's
definition); every tier's time is its data's; the conversion-copy release needs a dump on
the new engine. File-browser sync single-flight + no empty kept folder (R-695); the kept
view joins the folder's owning group, language switch resyncs (R-691); a restore-generated
login is not shown as the password (R-694). Red-proofs in
felhom.eu/documentation/audits/version-travel-2026-09-26/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-26 10:35:22 +02:00

71 lines
3.0 KiB
Go

package web
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/infra"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
// R-691 (v0.275.0) — the read-only „Megőrzött adatok" view could not open nextcloud's kept folder
// (`www-data` 33, mode 0770; the view runs as 1000). The rule: join the folder's OWNING GROUP when it
// is group-readable — never root's, never the view's own — and change nothing on the household's files.
//
// COMPANION RED-PROOF (REPORT.md): return nil from keptReadGroups — the compose then carries no
// group_add and this fails at "group_add missing".
func TestR691_KeptReadGroups(t *testing.T) {
owners := map[string]struct {
gid int
mode os.FileMode
}{
"/d/kept/nextcloud/2026-09-25_141014": {33, 0o770}, // the measured case → 33
"/d/appdata/nextcloud": {33, 0o770}, // the same group once
"/d/kept/romm/x": {1000, 0o755}, // the view's own group: nothing to add
"/d/kept/secret/x": {0, 0o750}, // root's group: NEVER
"/d/kept/private/x": {999, 0o700}, // not group-readable: adding it would not help
"/d/kept/jellyfin/x": {911, 0o750}, // another readable group → 911
}
var items []stacks.KeptItem
for p := range owners {
items = append(items, stacks.KeptItem{Path: p})
}
got := keptReadGroups(items, func(p string) (int, os.FileMode, bool) {
o, ok := owners[p]
return o.gid, o.mode, ok
})
if len(got) != 2 || got[0] != 33 || got[1] != 911 {
t.Fatalf("groups = %v, want [33 911]", got)
}
compose := infra.RenderFileBrowserCompose("example.hu", keptBindLines([]stacks.KeptItem{{Path: "/d/kept/nextcloud/2026-09-25_141014", App: "nextcloud"}}), got...)
if !strings.Contains(compose, "group_add:\n - \"33\"\n - \"911\"") {
t.Fatalf("group_add missing or malformed:\n%s", compose)
}
if !strings.Contains(compose, ":/srv/"+infra.FileBrowserKeptMount+"/") || !strings.Contains(compose, ":ro") {
t.Fatalf("the kept bind must stay read-only:\n%s", compose)
}
// No kept groups → the compose is exactly what it was before v0.275.0.
if strings.Contains(infra.RenderFileBrowserCompose("example.hu", nil), "group_add") {
t.Fatal("a box with no kept data got a group_add")
}
}
// The source's name follows the box's language: switching the language triggers the file-browser sync
// that writes it (before, it waited for the next unrelated change — seen on 9202 2026-09-25).
func TestR691_ALanguageSwitchResyncsTheFileBrowser(t *testing.T) {
s := noteServer(t)
called := 0
s.langSwitchSync = func() { called++ }
req := httptest.NewRequest(http.MethodPost, "/settings/language", strings.NewReader(url.Values{"lang": {"en"}}.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
s.languageSwitchHandler(rec, req)
if rec.Code != http.StatusFound || called != 1 {
t.Fatalf("status %d, file-browser syncs %d — want a redirect and one sync", rec.Code, called)
}
}