test(hydra): add snapshots for login & consent requests

GitOrigin-RevId: 47d041cf207af6c3e9e21bf3016e5ea0cf044344
This commit is contained in:
Patrik 2025-08-21 10:08:09 +02:00 committed by ory-bot
parent a043b43ceb
commit ee39bdb5bd
29 changed files with 102 additions and 127 deletions

View File

@ -5,4 +5,3 @@
"message": "uuid: incorrect UUID length 10 in string \"not-a-uuid\""
}
}

View File

@ -5,4 +5,3 @@
"message": "uuid: incorrect UUID length 10 in string \"not-a-uuid\""
}
}

View File

@ -271,7 +271,7 @@ func TestHandler(t *testing.T) {
t.Run("endpoint="+tc.name, func(t *testing.T) {
body := getCourierMessag(tc.s, "not-a-uuid")
snapshotx.SnapshotTJSONString(t, body.String())
snapshotx.SnapshotTJSON(t, body.Raw)
})
}
})
@ -279,7 +279,7 @@ func TestHandler(t *testing.T) {
for _, tc := range tss {
t.Run("endpoint="+tc.name, func(t *testing.T) {
body := getCourierMessag(tc.s, uuid.Nil.String())
snapshotx.SnapshotTJSONString(t, body.String())
snapshotx.SnapshotTJSON(t, body.Raw)
})
}
})

View File

@ -69,7 +69,6 @@ require (
github.com/ssoready/hyrumtoken v1.0.0
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/pretty v1.2.1
github.com/tidwall/sjson v1.2.5
github.com/urfave/negroni v1.0.0
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0
@ -199,6 +198,7 @@ require (
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect

View File

@ -7,93 +7,99 @@ import (
"bytes"
"encoding/json"
"fmt"
"slices"
"strings"
"testing"
"github.com/tidwall/gjson"
"github.com/tidwall/pretty"
"github.com/ory/x/stringslice"
"github.com/bradleyjkemp/cupaloy/v2"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
type (
ExceptOpt interface {
apply(t *testing.T, raw []byte) []byte
Opt = func(*options)
options struct {
modifiers []func(t *testing.T, raw []byte) []byte
name string
}
exceptPaths []string
exceptNestedKeys []string
replacement struct{ str, replacement string }
)
func (e exceptPaths) apply(t *testing.T, raw []byte) []byte {
for _, ee := range e {
var err error
raw, err = sjson.DeleteBytes(raw, ee)
require.NoError(t, err)
func ExceptPaths(keys ...string) Opt {
return func(o *options) {
o.modifiers = append(o.modifiers, func(t *testing.T, raw []byte) []byte {
for _, key := range keys {
var err error
raw, err = sjson.DeleteBytes(raw, key)
require.NoError(t, err)
}
return raw
})
}
return raw
}
func (e exceptNestedKeys) apply(t *testing.T, raw []byte) []byte {
parsed := gjson.ParseBytes(raw)
require.True(t, parsed.IsObject() || parsed.IsArray())
return deleteMatches(t, "", parsed, e, []string{}, raw)
}
func (r *replacement) apply(_ *testing.T, raw []byte) []byte {
return bytes.ReplaceAll(raw, []byte(r.str), []byte(r.replacement))
}
func ExceptPaths(keys ...string) ExceptOpt {
return exceptPaths(keys)
}
func ExceptNestedKeys(nestedKeys ...string) ExceptOpt {
return exceptNestedKeys(nestedKeys)
}
func WithReplacement(str, replace string) ExceptOpt {
return &replacement{str: str, replacement: replace}
}
func SnapshotTJSON(t *testing.T, compare []byte, except ...ExceptOpt) {
t.Helper()
for _, e := range except {
compare = e.apply(t, compare)
func ExceptNestedKeys(nestedKeys ...string) Opt {
return func(o *options) {
o.modifiers = append(o.modifiers, func(t *testing.T, raw []byte) []byte {
parsed := gjson.ParseBytes(raw)
require.True(t, parsed.IsObject() || parsed.IsArray())
return deleteMatches(t, "", parsed, nestedKeys, []string{}, raw)
})
}
cupaloy.New(
cupaloy.CreateNewAutomatically(true),
cupaloy.FailOnUpdate(true),
cupaloy.SnapshotFileExtension(".json"),
).SnapshotT(t, pretty.Pretty(compare))
}
func SnapshotTJSONString(t *testing.T, str string, except ...ExceptOpt) {
t.Helper()
SnapshotTJSON(t, []byte(str), except...)
func WithReplacement(str, replace string) Opt {
return func(o *options) {
o.modifiers = append(o.modifiers, func(t *testing.T, raw []byte) []byte {
return bytes.ReplaceAll(raw, []byte(str), []byte(replace))
})
}
}
func SnapshotT(t *testing.T, actual interface{}, except ...ExceptOpt) {
func WithName(name string) Opt {
return func(o *options) {
o.name = name
}
}
func newOptions(opts ...Opt) *options {
o := &options{}
for _, opt := range opts {
opt(o)
}
return o
}
func (o *options) applyModifiers(t *testing.T, compare []byte) []byte {
for _, modifier := range o.modifiers {
compare = modifier(t, compare)
}
return compare
}
var snapshot = cupaloy.New(cupaloy.SnapshotFileExtension(".json"))
func SnapshotTJSON[C ~string | ~[]byte](t *testing.T, compare C, opts ...Opt) {
SnapshotT(t, json.RawMessage(compare), opts...)
}
func SnapshotT(t *testing.T, actual any, opts ...Opt) {
t.Helper()
compare, err := json.MarshalIndent(actual, "", " ")
require.NoErrorf(t, err, "%+v", actual)
for _, e := range except {
compare = e.apply(t, compare)
}
cupaloy.New(
cupaloy.CreateNewAutomatically(true),
cupaloy.FailOnUpdate(true),
cupaloy.SnapshotFileExtension(".json"),
).SnapshotT(t, compare)
o := newOptions(opts...)
compare = o.applyModifiers(t, compare)
if o.name == "" {
snapshot.SnapshotT(t, compare)
} else {
name := strings.ReplaceAll(t.Name()+"_"+o.name, "/", "-")
require.NoError(t, snapshot.SnapshotWithName(name, compare))
}
}
// SnapshotTExcept is deprecated in favor of SnapshotT with ExceptOpt.
// SnapshotTExcept is deprecated in favor of SnapshotT with Opt.
//
// DEPRECATED: please use SnapshotT instead
func SnapshotTExcept(t *testing.T, actual interface{}, except []string) {
@ -105,11 +111,7 @@ func SnapshotTExcept(t *testing.T, actual interface{}, except []string) {
require.NoError(t, err, "%s", e)
}
cupaloy.New(
cupaloy.CreateNewAutomatically(true),
cupaloy.FailOnUpdate(true),
cupaloy.SnapshotFileExtension(".json"),
).SnapshotT(t, compare)
snapshot.SnapshotT(t, compare)
}
func deleteMatches(t *testing.T, key string, result gjson.Result, matches []string, parents []string, content []byte) []byte {
@ -132,7 +134,7 @@ func deleteMatches(t *testing.T, key string, result gjson.Result, matches []stri
})
}
if stringslice.Has(matches, key) {
if slices.Contains(matches, key) {
content, err := sjson.DeleteBytes(content, strings.Join(path, "."))
require.NoError(t, err)
return content
@ -140,25 +142,3 @@ func deleteMatches(t *testing.T, key string, result gjson.Result, matches []stri
return content
}
// SnapshotTExceptMatchingKeys works like SnapshotTExcept but deletes keys that match the given matches recursively.
//
// So instead of having deeply nested keys like `foo.bar.baz.0.key_to_delete` you can have `key_to_delete` and
// all occurences of `key_to_delete` will be removed.
//
// DEPRECATED: please use SnapshotT instead
func SnapshotTExceptMatchingKeys(t *testing.T, actual interface{}, matches []string) {
t.Helper()
compare, err := json.MarshalIndent(actual, "", " ")
require.NoError(t, err, "%+v", actual)
parsed := gjson.ParseBytes(compare)
require.True(t, parsed.IsObject() || parsed.IsArray())
compare = deleteMatches(t, "", parsed, matches, []string{}, compare)
cupaloy.New(
cupaloy.CreateNewAutomatically(true),
cupaloy.FailOnUpdate(true),
cupaloy.SnapshotFileExtension(".json"),
).SnapshotT(t, compare)
}

View File

@ -67,8 +67,12 @@
"text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.",
"type": "info",
"context": {
"available_credential_types": ["oidc"],
"available_providers": ["secondProvider"],
"available_credential_types": [
"oidc"
],
"available_providers": [
"secondProvider"
],
"duplicateIdentifier": "email-exist-with-oidc-strategy-lh-true@ory.sh",
"duplicate_identifier": "email-exist-with-oidc-strategy-lh-true@ory.sh",
"provider": "generic"
@ -80,4 +84,3 @@
"requested_aal": "aal1",
"state": "choose_method"
}

View File

@ -67,8 +67,12 @@
"text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.",
"type": "info",
"context": {
"available_credential_types": ["oidc"],
"available_providers": ["secondProvider"],
"available_credential_types": [
"oidc"
],
"available_providers": [
"secondProvider"
],
"duplicateIdentifier": "email-exist-with-oidc-strategy-lh-true@ory.sh",
"duplicate_identifier": "email-exist-with-oidc-strategy-lh-true@ory.sh",
"provider": "generic"
@ -80,4 +84,3 @@
"requested_aal": "aal1",
"state": "choose_method"
}

View File

@ -67,8 +67,12 @@
"text": "You tried to sign in with \"email-exist-with-oidc-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-oidc-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.",
"type": "info",
"context": {
"available_credential_types": ["oidc"],
"available_providers": ["secondProvider"],
"available_credential_types": [
"oidc"
],
"available_providers": [
"secondProvider"
],
"duplicateIdentifier": "email-exist-with-oidc-strategy-lh-true@ory.sh",
"duplicate_identifier": "email-exist-with-oidc-strategy-lh-true@ory.sh",
"provider": "generic"
@ -80,4 +84,3 @@
"requested_aal": "aal1",
"state": "choose_method"
}

View File

@ -84,7 +84,9 @@
"text": "You tried to sign in with \"email-exist-with-password-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.",
"type": "info",
"context": {
"available_credential_types": ["password"],
"available_credential_types": [
"password"
],
"available_providers": [],
"duplicateIdentifier": "email-exist-with-password-strategy-lh-true@ory.sh",
"duplicate_identifier": "email-exist-with-password-strategy-lh-true@ory.sh",
@ -97,4 +99,3 @@
"requested_aal": "aal1",
"state": "choose_method"
}

View File

@ -84,7 +84,9 @@
"text": "You tried to sign in with \"email-exist-with-password-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.",
"type": "info",
"context": {
"available_credential_types": ["password"],
"available_credential_types": [
"password"
],
"available_providers": [],
"duplicateIdentifier": "email-exist-with-password-strategy-lh-true@ory.sh",
"duplicate_identifier": "email-exist-with-password-strategy-lh-true@ory.sh",
@ -97,4 +99,3 @@
"requested_aal": "aal1",
"state": "choose_method"
}

View File

@ -84,7 +84,9 @@
"text": "You tried to sign in with \"email-exist-with-password-strategy-lh-true@ory.sh\", but that email is already used by another account. Sign in to your account with one of the options below to add your account \"email-exist-with-password-strategy-lh-true@ory.sh\" at \"generic\" as another way to sign in.",
"type": "info",
"context": {
"available_credential_types": ["password"],
"available_credential_types": [
"password"
],
"available_providers": [],
"duplicateIdentifier": "email-exist-with-password-strategy-lh-true@ory.sh",
"duplicate_identifier": "email-exist-with-password-strategy-lh-true@ory.sh",
@ -97,4 +99,3 @@
"requested_aal": "aal1",
"state": "choose_method"
}

View File

@ -555,7 +555,7 @@ func TestPopulateRegistrationMethod(t *testing.T) {
fh, ok := s.(registration.FormHydrator)
require.True(t, ok)
toSnapshot := func(t *testing.T, f node.Nodes, except ...snapshotx.ExceptOpt) {
toSnapshot := func(t *testing.T, f node.Nodes, except ...snapshotx.Opt) {
t.Helper()
// The CSRF token has a unique value that messes with the snapshot - ignore it.
f.ResetNodes("csrf_token")

View File

@ -411,7 +411,7 @@ func TestCompleteLogin(t *testing.T) {
}
assert.NotEmpty(t, gjson.Get(body, "id").String(), "%s", body)
snapshotx.SnapshotTExceptMatchingKeys(t, json.RawMessage(body), []string{"value", "src", "nonce", "action", "request_url", "issued_at", "expires_at", "created_at", "updated_at", "id", "onclick"})
snapshotx.SnapshotTJSON(t, body, snapshotx.ExceptNestedKeys("value", "src", "nonce", "action", "request_url", "issued_at", "expires_at", "created_at", "updated_at", "id", "onclick"))
assert.Equal(t, text.NewInfoLoginWebAuthnPasswordless().Text, gjson.Get(body, "ui.messages.0.text").String(), "%s", body)
values.Set(node.WebAuthnLogin, string(loginFixtureSuccessResponseInvalid))

View File

@ -561,7 +561,7 @@ func TestPopulateRegistrationMethod(t *testing.T) {
fh, ok := s.(registration.FormHydrator)
require.True(t, ok)
toSnapshot := func(t *testing.T, f node.Nodes, except ...snapshotx.ExceptOpt) {
toSnapshot := func(t *testing.T, f node.Nodes, except ...snapshotx.Opt) {
t.Helper()
// The CSRF token has a unique value that messes with the snapshot - ignore it.
f.ResetNodes("csrf_token")