Files
felhom-controller/controller/internal/web/system_memory_test.go
T
admin 3286c7faaf v0.143.0 — guest RAM resize UI (R-24) — MinAgent: 0.90.0
The customer sees the guest's current memory + allowed range on the Rendszer settings
page and resizes it. The controller proxies + maps the agent's machine code to Hungarian;
the agent (felhom-agent v0.90.0) enforces every bound and applies live (no reboot).

agentapi: GuestMemory + ResizeMemory; ruled 412 -> *MemoryRefusedError (code+bounds);
pre-0.90 agent 404 -> typed *StatusError. Capability: FeatureGuestMemoryResize +
featureMinAgent 0.90.0 + a featureProbes row (type-asserts GuestMemory so the shared
SupportProber/netAgent are untouched).

UI (internal/web/system_memory_handlers.go, settings_system.html): "Szerver memoria (RAM)"
card + number input; POST /api/system/memory/resize -> capability gate -> agent -> flash.
JS confirm only on shrink. Code->Hungarian map; agent-outdated hides the control;
agent-unreachable falls back to the guest's /proc/meminfo. Agent English never shown raw.

Tests: agentapi (decode, 404, refusal-code, capability table) + web handler (success/
below_usage_floor/agent_outdated). Gates + go build/vet/test all pass.
2026-07-17 19:10:00 +02:00

113 lines
3.9 KiB
Go

package web
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
)
// fakeMemAgent is the memAgent seam for handler tests. AgentVersion drives the capability gate
// (version fast-path); NetVerifyStatus is the SupportProber stub.
type fakeMemAgent struct {
info agentapi.GuestMemoryInfo
resizeRes agentapi.MemoryResizeResult
resizeErr error
ver string
calls int
}
func (f *fakeMemAgent) GuestMemory(context.Context) (agentapi.GuestMemoryInfo, error) {
return f.info, nil
}
func (f *fakeMemAgent) ResizeMemory(_ context.Context, _ int64) (agentapi.MemoryResizeResult, error) {
f.calls++
return f.resizeRes, f.resizeErr
}
func (f *fakeMemAgent) NetVerifyStatus(context.Context) (agentapi.NetVerifyStatus, error) {
return agentapi.NetVerifyStatus{Phase: "none"}, nil
}
func (f *fakeMemAgent) AgentVersion() string { return f.ver }
func postResize(t *testing.T, s *Server, body string) *httptest.ResponseRecorder {
t.Helper()
r := httptest.NewRequest(http.MethodPost, "/api/system/memory/resize", strings.NewReader(body))
w := httptest.NewRecorder()
s.ServeSystemAPI(w, r)
return w
}
// Success → 200 with the Hungarian old→new message.
func TestMemoryResize_Success(t *testing.T) {
s := testServer(t)
agent := &fakeMemAgent{ver: "0.90.0", resizeRes: agentapi.MemoryResizeResult{OldMB: 8192, NewMB: 12288}}
s.memAgentFn = func() (memAgent, error) { return agent, nil }
w := postResize(t, s, `{"memory_mb":12288}`)
if w.Code != http.StatusOK {
t.Fatalf("resize = %d (%s), want 200", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "A memória átméretezése megtörtént: 8192 MB → 12288 MB.") {
t.Errorf("success message missing: %s", w.Body.String())
}
if agent.calls != 1 {
t.Errorf("ResizeMemory calls = %d, want 1", agent.calls)
}
}
// below_usage_floor refusal → 412, the mapped Hungarian message, and the machine code (agent English
// never shown raw).
func TestMemoryResize_BelowUsageFloor(t *testing.T) {
s := testServer(t)
agent := &fakeMemAgent{
ver: "0.90.0",
resizeErr: &agentapi.MemoryRefusedError{
Code: "below_usage_floor",
Bounds: agentapi.GuestMemoryInfo{UsageMB: 3000, FloorMB: 3512, MinMB: 2048, MaxMB: 14336},
Msg: "requested 3300 MB is too close to current usage 3000 MB (floor 3512 MB)",
},
}
s.memAgentFn = func() (memAgent, error) { return agent, nil }
w := postResize(t, s, `{"memory_mb":3300}`)
if w.Code != http.StatusPreconditionFailed {
t.Fatalf("below_usage_floor = %d (%s), want 412", w.Code, w.Body.String())
}
body := w.Body.String()
if !strings.Contains(body, `"code":"below_usage_floor"`) {
t.Errorf("machine code missing: %s", body)
}
if !strings.Contains(body, "túl közel van a jelenlegi felhasználáshoz (3000 MB)") {
t.Errorf("mapped Hungarian message missing: %s", body)
}
// The agent's raw English must NEVER surface.
if strings.Contains(body, "requested 3300 MB is too close") {
t.Errorf("agent English leaked to the customer: %s", body)
}
}
// A pre-0.90 agent (version < MinAgent) → the capability gate refuses up front with agent_outdated,
// and ResizeMemory is NEVER called.
func TestMemoryResize_AgentOutdated(t *testing.T) {
s := testServer(t)
agent := &fakeMemAgent{ver: "0.89.0"}
s.memAgentFn = func() (memAgent, error) { return agent, nil }
w := postResize(t, s, `{"memory_mb":12288}`)
if w.Code != http.StatusPreconditionFailed {
t.Fatalf("outdated agent = %d (%s), want 412", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), `"code":"agent_outdated"`) {
t.Errorf("agent_outdated code missing: %s", w.Body.String())
}
if !strings.Contains(w.Body.String(), "rendszerfrissítése szükséges") {
t.Errorf("outdated Hungarian note missing: %s", w.Body.String())
}
if agent.calls != 0 {
t.Errorf("ResizeMemory called %d times on a gated request — must be 0", agent.calls)
}
}