b6810f14ff
gates / gates (push) Successful in 23s
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
71 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|