Files
upstream-ory-kratos/pkg/testhelpers/selfservice_settings.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

277 lines
7.9 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package testhelpers
import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/ory/kratos/driver"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
kratos "github.com/ory/kratos/pkg/httpclient"
"github.com/ory/kratos/selfservice/flow/settings"
"github.com/ory/kratos/x"
"github.com/ory/x/ioutilx"
"github.com/ory/x/urlx"
)
func NewSettingsUIFlowEchoServer(t *testing.T, reg driver.Registry) *httptest.Server {
ctx := context.Background()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
e, err := reg.SettingsFlowPersister().GetSettingsFlow(r.Context(), x.ParseUUID(r.URL.Query().Get("flow")))
require.NoError(t, err)
reg.Writer().Write(w, r, e)
}))
reg.Config().MustSet(ctx, config.ViperKeySelfServiceSettingsURL, ts.URL+"/settings-ts")
t.Cleanup(ts.Close)
return ts
}
func InitializeSettingsFlowViaBrowser(t *testing.T, client *http.Client, isSPA bool, ts *httptest.Server) *kratos.SettingsFlow {
t.Helper()
publicClient := NewSDKCustomClient(ts, client)
req, err := http.NewRequest("GET", ts.URL+settings.RouteInitBrowserFlow, nil)
require.NoError(t, err)
if isSPA {
req.Header.Set("Accept", "application/json")
}
res, err := client.Do(req)
require.NoError(t, err)
var flowID string
if isSPA {
flowID = gjson.GetBytes(x.MustReadAll(res.Body), "id").String()
} else {
flowID = res.Request.URL.Query().Get("flow")
}
require.NoError(t, res.Body.Close())
rs, res, err := publicClient.FrontendAPI.GetSettingsFlow(context.Background()).Id(flowID).Execute()
require.NoError(t, err, "%s", ioutilx.MustReadAll(res.Body))
assert.Empty(t, rs.Active)
return rs
}
func InitializeSettingsFlowViaAPI(t *testing.T, client *http.Client, ts *httptest.Server) *kratos.SettingsFlow {
publicClient := NewSDKCustomClient(ts, client)
rs, _, err := publicClient.FrontendAPI.CreateNativeSettingsFlow(context.Background()).Execute()
require.NoError(t, err)
assert.Empty(t, rs.Active)
return rs
}
func EncodeFormAsJSON(t *testing.T, isApi bool, values url.Values) (payload string) {
if !isApi {
return values.Encode()
}
payload = "{}"
for k := range values {
var err error
payload, err = sjson.Set(payload, strings.ReplaceAll(k, ".", "\\."), values.Get(k))
require.NoError(t, err)
}
return payload
}
func ExpectStatusCode(isAPI bool, api, browser int) int {
if isAPI {
return api
}
return browser
}
func ExpectURL(isAPI bool, api, browser string) string {
if isAPI {
return api
}
return browser
}
func NewSettingsUITestServer(t *testing.T, conf *config.Config) *httptest.Server {
router := http.NewServeMux()
router.HandleFunc("GET /settings", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
router.HandleFunc("GET /login", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
})
ts := httptest.NewServer(router)
t.Cleanup(ts.Close)
ctx := context.Background()
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsURL, ts.URL+"/settings")
conf.MustSet(ctx, config.ViperKeySelfServiceLoginUI, ts.URL+"/login")
return ts
}
func NewSettingsUIEchoServer(t *testing.T, reg *driver.RegistryDefault) *httptest.Server {
router := http.NewServeMux()
router.HandleFunc("GET /settings", func(w http.ResponseWriter, r *http.Request) {
res, err := reg.SettingsFlowPersister().GetSettingsFlow(r.Context(), x.ParseUUID(r.URL.Query().Get("flow")))
require.NoError(t, err)
reg.Writer().Write(w, r, res)
})
router.HandleFunc("GET /login", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
})
ts := httptest.NewServer(router)
t.Cleanup(ts.Close)
ctx := context.Background()
reg.Config().MustSet(ctx, config.ViperKeySelfServiceSettingsURL, ts.URL+"/settings")
reg.Config().MustSet(ctx, config.ViperKeySelfServiceLoginUI, ts.URL+"/login")
return ts
}
func NewSettingsLoginAcceptAPIServer(t *testing.T, publicClient *kratos.APIClient, conf *config.Config) *httptest.Server {
var called int
loginTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, 0, called)
called++
conf.MustSet(r.Context(), config.ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, "5m")
res, _, err := publicClient.FrontendAPI.GetLoginFlow(context.Background()).Id(r.URL.Query().Get("flow")).Execute()
require.NoError(t, err)
require.NotEmpty(t, res.RequestUrl)
redir := urlx.ParseOrPanic(res.RequestUrl).Query().Get("return_to")
t.Logf("Redirecting to: %s", redir)
http.Redirect(w, r, redir, http.StatusFound)
}))
t.Cleanup(func() {
loginTS.Close()
})
ctx := context.Background()
conf.MustSet(ctx, config.ViperKeySelfServiceLoginUI, loginTS.URL+"/login")
return loginTS
}
// AddAndLoginIdentity adds the given identity to the store (like a registration flow) and returns an http.Client
// which contain their session.
func AddAndLoginIdentity(t *testing.T, reg *driver.RegistryDefault, i *identity.Identity) (uuid.UUID, *http.Client) {
loggedIn := AddAndLoginIdentities(t, reg, map[string]*identity.Identity{"": i})[""]
return loggedIn.ID, loggedIn.Client
}
// AddAndLoginIdentities adds the given identities to the store (like a registration flow) and returns http.Clients
// which contain their sessions.
func AddAndLoginIdentities(t *testing.T, reg *driver.RegistryDefault, ids map[string]*identity.Identity) map[string]struct {
Client *http.Client
ID uuid.UUID
} {
result := map[string]struct {
Client *http.Client
ID uuid.UUID
}{}
r := http.NewServeMux()
s := httptest.NewServer(r)
defer s.Close()
for k, i := range ids {
i.ID = uuid.Must(uuid.NewV4())
tid := x.NewUUID().String()
route, _ := MockSessionCreateHandlerWithIdentity(t, reg, i)
location := "/sessions/set/" + tid
r.Handle("GET "+location, route)
result[k] = struct {
Client *http.Client
ID uuid.UUID
}{
Client: NewSessionClient(t, s.URL+location),
ID: i.ID,
}
}
return result
}
func SettingsMakeRequest(
t *testing.T,
isAPI bool,
isSPA bool,
f *kratos.SettingsFlow,
hc *http.Client,
values string,
) (string, *http.Response) {
require.NotEmpty(t, f.Ui.Action)
req := NewPostRequest(t, isSPA || isAPI, f.Ui.Action, bytes.NewBufferString(values))
if isSPA || isAPI {
req.Header.Set("Accept", "application/json")
}
if isSPA {
req.Header.Set("Sec-Fetch-Mode", "cors")
}
res, err := hc.Do(req)
require.NoError(t, err, "action: %s", f.Ui.Action)
defer func() { _ = res.Body.Close() }()
return string(ioutilx.MustReadAll(res.Body)), res
}
// SubmitSettingsForm initiates a settings flow (for Browser and API!), fills out the form and modifies
// the form values with `withValues`, and submits the form. If completed, it will return the flow as JSON.
func SubmitSettingsForm(
t *testing.T,
isAPI bool,
isSPA bool,
hc *http.Client,
publicTS *httptest.Server,
withValues func(v url.Values),
expectedStatusCode int,
expectedURL string,
) string {
if hc == nil {
hc = NewTestClient(t)
if !isAPI {
hc = NewClientWithCookies(t)
}
}
hc.Transport = NewTransportWithLogger(hc.Transport, t)
var payload *kratos.SettingsFlow
if isAPI {
payload = InitializeSettingsFlowViaAPI(t, hc, publicTS)
} else {
payload = InitializeSettingsFlowViaBrowser(t, hc, isSPA, publicTS)
}
time.Sleep(time.Millisecond * 10) // add a bit of delay to allow `1ns` to time out.
values := SDKFormFieldsToURLValues(payload.Ui.Nodes)
withValues(values)
b, res := SettingsMakeRequest(t, isAPI, isSPA, payload, hc, EncodeFormAsJSON(t, isAPI || isSPA, values))
assert.EqualValues(t, expectedStatusCode, res.StatusCode, "%s", b)
assert.Contains(t, res.Request.URL.String(), expectedURL, "%+v\n\t%s", res.Request, b)
return b
}