Files
upstream-ory-kratos/selfservice/flow/error_test.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

166 lines
5.0 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package flow
import (
"encoding/json"
"net/http"
"net/url"
"testing"
"github.com/gofrs/uuid"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/text"
"github.com/ory/kratos/ui/container"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/x/httpx"
"github.com/ory/x/logrusx"
"github.com/ory/x/otelx"
)
type testCSRFTokenGenerator struct{}
func (t *testCSRFTokenGenerator) GenerateCSRFToken(_ *http.Request) string {
return "csrf_token_value"
}
// testFlow is a minimalistic flow implementation to satisfy interface and is used only in tests.
type testFlow struct {
// ID represents the flow's unique ID.
//
// required: true
ID uuid.UUID `json:"id" faker:"-" db:"id" rw:"r"`
// Type represents the flow's type which can be either "api" or "browser", depending on the flow interaction.
//
// required: true
Type Type `json:"type" db:"type" faker:"flow_type"`
// RequestURL is the initial URL that was requested from Ory Kratos. It can be used
// to forward information contained in the URL's path or query for example.
//
// required: true
RequestURL string `json:"request_url" db:"request_url"`
// UI contains data which must be shown in the user interface.
//
// required: true
UI *container.Container `json:"ui" db:"ui"`
// Flow State
//
// The state represents the state of the verification flow.
//
// - choose_method: ask the user to choose a method (e.g. recover account via email)
// - sent_email: the email has been sent to the user
// - passed_challenge: the request was successful and the recovery challenge was passed.
//
// required: true
State State `json:"state" db:"state"`
}
func (t *testFlow) GetID() uuid.UUID {
return t.ID
}
func (t *testFlow) GetType() Type {
return t.Type
}
func (t *testFlow) GetRequestURL() string {
return t.RequestURL
}
func (t *testFlow) AppendTo(url *url.URL) *url.URL {
return AppendFlowTo(url, t.ID)
}
func (t *testFlow) GetUI() *container.Container {
return t.UI
}
func (t *testFlow) GetState() State {
return t.State
}
func (t *testFlow) GetFlowName() FlowName {
return FlowName("test")
}
func (t *testFlow) SetState(state State) {
t.State = state
}
func (t *testFlow) GetTransientPayload() json.RawMessage {
return nil
}
func newTestFlow(r *http.Request, flowType Type) Flow {
id := x.NewUUID()
requestURL := x.RequestURL(r).String()
ui := &container.Container{
Method: "POST",
Action: "/test",
}
ui.Nodes.Append(node.NewInputField("traits.username", nil, node.PasswordGroup, node.InputAttributeTypeText, node.WithRequiredInputAttribute))
ui.Nodes.Append(node.NewInputField("traits.password", nil, node.PasswordGroup, node.InputAttributeTypePassword, node.WithRequiredInputAttribute))
return &testFlow{
ID: id,
UI: ui,
RequestURL: requestURL,
Type: flowType,
}
}
func prepareTraits(username, password string) identity.Traits {
payload := struct {
Username string `json:"username"`
Password string `json:"password"`
}{username, password}
data, _ := json.Marshal(payload)
return data
}
func TestHandleHookError(t *testing.T) {
r := &http.Request{URL: &url.URL{RawQuery: ""}}
logger := logrusx.New("kratos", "test", logrusx.ForceLevel(logrus.FatalLevel))
l := &x.BasicRegistry{L: logger, C: httpx.NewResilientClient(), T: otelx.NewNoop()}
csrf := testCSRFTokenGenerator{}
f := newTestFlow(r, TypeBrowser)
tr := prepareTraits("foo", "bar")
t.Run("case=fill_in_traits", func(t *testing.T) {
ve := schema.NewValidationListError([]*schema.ValidationError{schema.NewHookValidationError("traits.username", "invalid username", text.Messages{})})
err := HandleHookError(nil, r, f, tr, node.PasswordGroup, ve, l, &csrf)
assert.ErrorIs(t, err, ve)
if assert.NotEmpty(t, f.GetUI()) {
ui := f.GetUI()
assert.Len(t, ui.Nodes, 3)
assert.ElementsMatch(t, ui.Nodes,
node.Nodes{
&node.Node{Type: node.Input, Group: node.PasswordGroup, Attributes: &node.InputAttributes{Name: "traits.username", Type: node.InputAttributeTypeText, FieldValue: "foo", Required: true}, Meta: &node.Meta{}},
&node.Node{Type: node.Input, Group: node.PasswordGroup, Attributes: &node.InputAttributes{Name: "traits.password", Type: node.InputAttributeTypePassword, FieldValue: "bar", Required: true}, Meta: &node.Meta{}},
&node.Node{Type: node.Input, Group: node.DefaultGroup, Attributes: &node.InputAttributes{Name: "csrf_token", Type: node.InputAttributeTypeHidden, FieldValue: "csrf_token_value", Required: true}},
})
}
})
t.Run("case=unmarshal_fail", func(t *testing.T) {
ve := schema.NewValidationListError([]*schema.ValidationError{schema.NewHookValidationError("traits.username", "invalid username", text.Messages{})})
err := HandleHookError(nil, r, f, []byte("garbage"), node.PasswordGroup, ve, l, &csrf)
var jsonErr *json.SyntaxError
assert.ErrorAs(t, err, &jsonErr)
})
}