package platform import ( "encoding/base64" "encoding/json" "testing" ) // makeJWT builds a syntactically valid (unsigned) JWT carrying the given claims. // UserFromToken never verifies the signature, so a dummy header + sig suffice. func makeJWT(claims map[string]any) string { payload, _ := json.Marshal(claims) seg := base64.RawURLEncoding.EncodeToString(payload) return "eyJhbGciOiJSUzI1NiJ9." + seg + ".sig" } func TestUserFromTokenPrefersIDToken(t *testing.T) { idTok := makeJWT(map[string]any{"sub": "u-1", "preferred_username": "alice"}) accessTok := makeJWT(map[string]any{"sub": "u-1", "name": "bob"}) u, err := UserFromToken(idTok, accessTok) if err != nil { t.Fatalf("UserFromToken: %v", err) } if u.ID != "u-1" { t.Errorf("id: got %q, want u-1", u.ID) } if u.Name != "alice" { t.Errorf("name should come from id_token preferred_username, got %q", u.Name) } } func TestUserFromTokenNamePriority(t *testing.T) { cases := []struct { name string claims map[string]any want string }{ {"preferred_username wins", map[string]any{"sub": "s", "preferred_username": "p", "name": "n", "email": "e"}, "p"}, {"name when no preferred", map[string]any{"sub": "s", "name": "n", "email": "e"}, "n"}, {"email when no name", map[string]any{"sub": "s", "email": "e@x"}, "e@x"}, {"sub as last resort", map[string]any{"sub": "s"}, "s"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { u, err := UserFromToken(makeJWT(c.claims), "") if err != nil { t.Fatalf("UserFromToken: %v", err) } if u.Name != c.want { t.Errorf("name: got %q, want %q", u.Name, c.want) } }) } } func TestUserFromTokenAvatarPriority(t *testing.T) { u, _ := UserFromToken(makeJWT(map[string]any{ "sub": "s", "avatar": "a.png", "picture": "p.png", }), "") if u.Avatar != "a.png" { t.Errorf("avatar should prefer 'avatar' claim, got %q", u.Avatar) } u2, _ := UserFromToken(makeJWT(map[string]any{"sub": "s", "picture": "p.png"}), "") if u2.Avatar != "p.png" { t.Errorf("avatar should fall back to 'picture', got %q", u2.Avatar) } u3, _ := UserFromToken(makeJWT(map[string]any{"sub": "s"}), "") if u3.Avatar != "" { t.Errorf("avatar should be empty when no claim, got %q", u3.Avatar) } } func TestUserFromTokenFallsBackToAccessToken(t *testing.T) { accessTok := makeJWT(map[string]any{"sub": "u-9", "name": "carol"}) u, err := UserFromToken("", accessTok) if err != nil { t.Fatalf("UserFromToken: %v", err) } if u.ID != "u-9" || u.Name != "carol" { t.Errorf("fallback to access_token failed: %+v", u) } } func TestUserFromTokenRejectsGarbage(t *testing.T) { if _, err := UserFromToken("not-a-jwt", ""); err == nil { t.Error("expected error for non-JWT token") } if _, err := UserFromToken("a.!!!notbase64!!!.c", ""); err == nil { t.Error("expected error for undecodable payload") } }