Files
o11y/pkg/modules/user/impluser/handler_test.go
T
zeekay 69b187636d Let Hanzo IAM answer who the caller is
o11y carried a second identity system: email/password sign-in, Google,
OIDC and SAML callbacks, self-registration, invites, reset-password
tokens, roles-on-users, SSO auth domains, and a pair of tokenizers that
minted and stored its own sessions. Hanzo IAM answers all of it, once,
at the edge. Two credential stores is one too many, and the one nobody
signs into is the one that rots.

What is left is Identity: WHO a request is, resolved from what the edge
asserted or from a service-account key, never issued here. The routes are
not merely unrouted — the handlers, stores, tables and templates behind
them are gone, and anonymous_test.go names each dead address so that
re-exempting one fails even though no route can come back.

The census moves with it: 366 addresses to 329, 353 typed ops to 319, ten
escape hatches to seven. zipdoc_gen.go loses the 34 descriptions whose
addresses left, because zip keys its doc registry by method and path, so
a description outliving its declaration attaches this module's prose to
whatever a host serves at that address. zipdoc_test.go pins that.

Cherry-picked from delete/o11y-identity 89854a68ae.
2026-09-08 23:24:35 -07:00

79 lines
2.9 KiB
Go

package impluser
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/hanzoai/o11y/pkg/types"
"github.com/hanzoai/o11y/pkg/types/authtypes"
)
// GetMyUser ANSWERS FROM THE CLAIMS, and this is the test that says so.
//
// The handler used to hold a Getter and SELECT the caller's row, which made
// o11y's own bookkeeping a precondition for rendering the console: a person the
// edge had authenticated, whose row was missing, got "user not found" on the one
// call the console uses to learn who it is. NewHandler now takes NOTHING — there
// is no store to reach — so the coupling is gone at the type level and this test
// pins the values that come out.
//
// The mutation that must turn this red: read any field from anywhere but the
// claims (a row, a config, a default) and the assertions below stop matching the
// claims they were built from.
func TestGetMyUserIsTheClaims(t *testing.T) {
claims := authtypes.Claims{
UserID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
OrgID: "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
Email: "[email protected]",
Principal: authtypes.PrincipalUser,
}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/v1/o11y/users/me", http.NoBody)
NewHandler().GetMyUser(rec, req.WithContext(authtypes.NewContextWithClaims(req.Context(), claims)))
if rec.Code != http.StatusOK {
t.Fatalf("status=%d body=%s, want 200", rec.Code, rec.Body)
}
var got struct {
Status string `json:"status"`
Data types.User `json:"data"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
t.Fatalf("unmarshal: %v (%s)", err, rec.Body)
}
if got.Data.ID.StringValue() != claims.UserID {
t.Errorf("id=%q, want the IAM subject %q", got.Data.ID.StringValue(), claims.UserID)
}
if got.Data.OrgID.StringValue() != claims.OrgID {
t.Errorf("orgId=%q, want the asserted org %q", got.Data.OrgID.StringValue(), claims.OrgID)
}
if got.Data.Email.StringValue() != claims.Email {
t.Errorf("email=%q, want the asserted address %q", got.Data.Email.StringValue(), claims.Email)
}
if got.Data.DisplayName != claims.Email {
t.Errorf("displayName=%q, want the asserted address %q — the seated row carried the same value", got.Data.DisplayName, claims.Email)
}
if got.Data.IsRoot {
t.Error("isRoot=true — an IAM session is never the local root user")
}
if got.Data.Status != types.UserStatusActive {
t.Errorf("status=%q, want active", got.Data.Status)
}
}
// No claims, no answer. The route is OpenAccess — "is anyone authenticated" —
// so the refusal has to come from here, and it has to be a refusal.
func TestGetMyUserWithoutClaimsIsUnauthenticated(t *testing.T) {
rec := httptest.NewRecorder()
NewHandler().GetMyUser(rec, httptest.NewRequest(http.MethodGet, "/v1/o11y/users/me", http.NoBody))
if rec.Code != http.StatusUnauthorized {
t.Fatalf("status=%d body=%s, want 401", rec.Code, rec.Body)
}
}