a43d0423d5
gates / gates (push) Successful in 46s
felhom.eu architecture/16 §4-§6 (build B). MinAgent 0.157.0 — the internal place and the partitions appear only when agent >= 0.158.0 reports them. Tests red-proved; four parity fixtures refreshed (only the new lines). Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
83 lines
4.1 KiB
Go
83 lines
4.1 KiB
Go
package stacks
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// D4 (felhom.eu architecture/16-storage-physical-identity.md §5, R-1068): a data-folder or media app on a
|
|
// box with NO drive is refused in words with the way forward — never the field name, never sys_drive.
|
|
// COMPANION RED-PROOF: delete the D4 branch in deploy.go → the plain required-field refusal comes back
|
|
// („a(z) "Médiatár útvonal" (HDD_PATH) mező kitöltése kötelező", the OMV test's sentence) and this fails.
|
|
func TestD4_NoDriveRefusalSpeaksInWords(t *testing.T) {
|
|
for name, yml := range map[string]string{
|
|
"media app": "display_name: Jellyfin\nmedia_library:\n folder: films\ndeploy_fields:\n - env_var: HDD_PATH\n label: Médiatár útvonal\n type: path\n required: true\n",
|
|
"data-folder app": "display_name: Paperless\ndeploy_fields:\n - env_var: HDD_PATH\n label: Adattárolási útvonal\n type: path\n required: true\n",
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
m := r553Manager(t, yml)
|
|
_, err := m.DeployStack(DeployRequest{StackName: "r553app", Values: map[string]string{}})
|
|
if err == nil {
|
|
t.Fatal("deploy was admitted on a box with no drive")
|
|
}
|
|
if !errors.Is(err, ErrNeedsDrive) {
|
|
t.Fatalf("want ErrNeedsDrive, got %v", err)
|
|
}
|
|
msg := err.Error()
|
|
if msg != "Ehhez az alkalmazáshoz egy adattároló kell. A Meghajtók oldalon adhatsz hozzá egyet." {
|
|
t.Errorf("the D4 sentence moved: %q", msg)
|
|
}
|
|
for _, bad := range []string{"HDD_PATH", "útvonal", "sys_drive", "kötelező"} {
|
|
if strings.Contains(msg, bad) {
|
|
t.Errorf("the refusal names %q — it must speak in words, not fields: %q", bad, msg)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A path field the household FILLED is not a D4 case: a missing path keeps its own refusal.
|
|
func TestD4_FilledPathKeepsItsOwnRefusal(t *testing.T) {
|
|
m := r553Manager(t, "display_name: X\ndeploy_fields:\n - env_var: HDD_PATH\n label: Adat\n type: path\n required: true\n")
|
|
_, err := m.DeployStack(DeployRequest{StackName: "r553app", Values: map[string]string{"HDD_PATH": "/definitely/not/here/d4"}})
|
|
if !errors.Is(err, ErrPathMissing) {
|
|
t.Fatalf("want ErrPathMissing for a filled, missing path, got %v", err)
|
|
}
|
|
}
|
|
|
|
// `16` §5 (v0.318.0): on a one-disk box with the internal place registered, a media app installs with NO extra step
|
|
// and NO sentence — its HDD_PATH lands on the internal place. Companion of TestD4_NoDriveRefusalSpeaksInWords (the
|
|
// same app, no drive → the refusal).
|
|
func TestD4_InternalPlaceIsTheMediaAppsDrive(t *testing.T) {
|
|
m := r553Manager(t, "display_name: Jellyfin\nmedia_library:\n folder: films\ndeploy_fields:\n - env_var: HDD_PATH\n label: Médiatár útvonal\n type: path\n required: true\n")
|
|
sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), log.New(io.Discard, "", 0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
belso := filepath.Join(t.TempDir(), "belso")
|
|
if err := sett.AddStoragePath(settings.StoragePath{Path: belso, IsDefault: true, Schedulable: true, Kind: settings.StorageKindInternal}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.settings = sett
|
|
m.isMountPoint = func(p string) bool { return filepath.Clean(p) == belso }
|
|
if got := m.defaultMediaDrive(); got != belso {
|
|
t.Fatalf("the default media drive: want the internal place %q, got %q", belso, got)
|
|
}
|
|
// The folder is deliberately NOT created: the deploy then stops at its path check, so no real `docker compose`
|
|
// can ever start on the machine running this test (tests that reach real docker act on DooPlex, R-650). The
|
|
// refusal naming the internal place proves HDD_PATH was filled with it — the step the household no longer does.
|
|
_, err = m.DeployStack(DeployRequest{StackName: "r553app", Values: map[string]string{}})
|
|
if errors.Is(err, ErrNeedsDrive) {
|
|
t.Fatalf("a box with the internal place must not be told it needs a drive: %v", err)
|
|
}
|
|
if !errors.Is(err, ErrPathMissing) || !strings.Contains(err.Error(), belso) {
|
|
t.Fatalf("want the media app's HDD_PATH filled with the internal place %q, got %v", belso, err)
|
|
}
|
|
}
|