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) } }