fix(session): real getMyProfiles shape {myprofiles:[...]} + active-profile detection [#767]
https://projects.knownelement.com/issues/767#note-4191
This commit is contained in:
+70
-14
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -361,14 +362,21 @@ func TestProfiles(t *testing.T) {
|
||||
if len(profs) != 3 {
|
||||
t.Fatalf("profiles = %+v, want 3", profs)
|
||||
}
|
||||
active := 0
|
||||
for _, p := range profs {
|
||||
if p.IsActive {
|
||||
active = p.ID
|
||||
for _, want := range []string{"Self-Service", "Hotliner", "Super-admin"} {
|
||||
found := false
|
||||
for _, p := range profs {
|
||||
if p.Name == want {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("profiles missing %q: %+v", want, profs)
|
||||
}
|
||||
}
|
||||
if active != 6 { // Super-admin is the fake's default active profile
|
||||
t.Errorf("default active profile = %d, want 6", active)
|
||||
// The active profile comes from GET /getActiveProfile, not from the
|
||||
// myprofiles rows (which carry no is_active in the real API).
|
||||
if active, err := c.GetActiveProfile(context.Background()); err != nil || active != 6 {
|
||||
t.Fatalf("GetActiveProfile = %d, %v; want 6 (Super-admin default)", active, err)
|
||||
}
|
||||
|
||||
if err := c.ChangeActiveProfile(context.Background(), 5); err != nil {
|
||||
@@ -377,20 +385,68 @@ func TestProfiles(t *testing.T) {
|
||||
if body := lastReq(t, srv).Body; body != `{"profiles_id":5}` {
|
||||
t.Errorf("changeActiveProfile body = %s", body)
|
||||
}
|
||||
profs, _ = c.GetMyProfiles(context.Background())
|
||||
for _, p := range profs {
|
||||
if p.ID == 5 && !p.IsActive {
|
||||
t.Errorf("profile 5 not active after switch: %+v", profs)
|
||||
}
|
||||
if p.ID == 6 && p.IsActive {
|
||||
t.Errorf("profile 6 still active after switch: %+v", profs)
|
||||
}
|
||||
if active, err := c.GetActiveProfile(context.Background()); err != nil || active != 5 {
|
||||
t.Errorf("GetActiveProfile after switch = %d, %v; want 5 (Hotliner)", active, err)
|
||||
}
|
||||
if err := c.ChangeActiveProfile(context.Background(), 999); !errors.Is(err, glpi.ErrValidation) {
|
||||
t.Errorf("unknown profile err = %v, want ErrValidation", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fake-drift guard: the fake must serve GLPI's REAL WRAPPED
|
||||
// getMyProfiles shape on the wire — {"myprofiles":[...]} — because the
|
||||
// bare-array guess is exactly what broke against prod (2026-09-03).
|
||||
func TestFakeMyProfilesWireShape(t *testing.T) {
|
||||
c, srv := newClient(t)
|
||||
if err := c.InitSession(context.Background()); err != nil {
|
||||
t.Fatalf("InitSession: %v", err)
|
||||
}
|
||||
sess := srv.Sessions()[0]
|
||||
req, _ := http.NewRequest("GET", srv.URL+"/apirest.php/getMyProfiles", nil)
|
||||
req.Header.Set("App-Token", appTok)
|
||||
req.Header.Set("Session-Token", sess)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("raw GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read body: %v", err)
|
||||
}
|
||||
var probe struct {
|
||||
MyProfiles []struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Entities []any `json:"entities"` // optional in the real API
|
||||
} `json:"myprofiles"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &probe); err != nil {
|
||||
t.Fatalf("getMyProfiles body is not the wrapped shape: %s", b)
|
||||
}
|
||||
if len(probe.MyProfiles) != 3 || probe.MyProfiles[2].Name != "Super-admin" {
|
||||
t.Errorf("wrapped rows = %+v", probe.MyProfiles)
|
||||
}
|
||||
// ...and the active-profile endpoint answers the wrapped object too.
|
||||
req2, _ := http.NewRequest("GET", srv.URL+"/apirest.php/getActiveProfile", nil)
|
||||
req2.Header.Set("App-Token", appTok)
|
||||
req2.Header.Set("Session-Token", sess)
|
||||
resp2, err := http.DefaultClient.Do(req2)
|
||||
if err != nil {
|
||||
t.Fatalf("raw GET active: %v", err)
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
b2, _ := io.ReadAll(resp2.Body)
|
||||
var probe2 struct {
|
||||
ActiveProfile struct {
|
||||
ID int `json:"id"`
|
||||
} `json:"active_profile"`
|
||||
}
|
||||
if err := json.Unmarshal(b2, &probe2); err != nil || probe2.ActiveProfile.ID != 6 {
|
||||
t.Fatalf("getActiveProfile body = %s (%v), want wrapped active_profile id 6", b2, err)
|
||||
}
|
||||
}
|
||||
|
||||
// THE agent-mode proof: with RequireChangeProfile=5 the fake rejects
|
||||
// change creation until the session's active profile IS 5 — the exact
|
||||
// Hotliner flow an agent env file drives via MGLPI_PROFILE_ID.
|
||||
|
||||
Reference in New Issue
Block a user