snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
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
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
This commit is contained in:
335
continuity/manager_test.go
Normal file
335
continuity/manager_test.go
Normal file
@@ -0,0 +1,335 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package continuity_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/ory/kratos/continuity"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/pkg"
|
||||
"github.com/ory/kratos/pkg/testhelpers"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/ioutilx"
|
||||
"github.com/ory/x/logrusx"
|
||||
)
|
||||
|
||||
type persisterTestCase struct {
|
||||
ro []continuity.ManagerOption
|
||||
wo []continuity.ManagerOption
|
||||
expected *persisterTestPayload
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
type persisterTestPayload struct {
|
||||
Foo string `json:"foo"`
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t,
|
||||
configx.WithValues(testhelpers.DefaultIdentitySchemaConfig("file://../test/stub/identity/empty.schema.json")),
|
||||
)
|
||||
|
||||
i := identity.NewIdentity("")
|
||||
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(t.Context(), i))
|
||||
|
||||
newServer := func(t *testing.T, p *continuity.Manager, tc *persisterTestCase) *httptest.Server {
|
||||
writer := herodot.NewJSONWriter(logrusx.New("", ""))
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("PUT /{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
store := continuity.NewCookieReferenceStore(reg.ContinuityCookieManager(r.Context()))
|
||||
if _, err := p.Pause(r.Context(), w, r, r.PathValue("name"), store, tc.ro...); err != nil {
|
||||
writer.WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
router.HandleFunc("POST /{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
store := continuity.NewCookieReferenceStore(reg.ContinuityCookieManager(r.Context()))
|
||||
if _, err := p.Pause(r.Context(), w, r, r.PathValue("name"), store, tc.ro...); err != nil {
|
||||
writer.WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := p.Continue(r.Context(), w, r, r.PathValue("name"), store, tc.wo...)
|
||||
if err != nil {
|
||||
writer.WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
writer.Write(w, r, c)
|
||||
})
|
||||
|
||||
router.HandleFunc("GET /{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
store := continuity.NewCookieReferenceStore(reg.ContinuityCookieManager(r.Context()))
|
||||
c, err := p.Continue(r.Context(), w, r, r.PathValue("name"), store, tc.ro...)
|
||||
if err != nil {
|
||||
writer.WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
writer.Write(w, r, c)
|
||||
})
|
||||
|
||||
router.HandleFunc("DELETE /{name}", func(w http.ResponseWriter, r *http.Request) {
|
||||
store := continuity.NewCookieReferenceStore(reg.ContinuityCookieManager(r.Context()))
|
||||
err := p.Abort(r.Context(), w, r, r.PathValue("name"), store)
|
||||
if err != nil {
|
||||
writer.WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(router)
|
||||
t.Cleanup(ts.Close)
|
||||
return ts
|
||||
}
|
||||
|
||||
newClient := func() *http.Client {
|
||||
return &http.Client{Jar: testhelpers.EasyCookieJar(t, nil), Transport: testhelpers.NewTestTransport(t)}
|
||||
}
|
||||
|
||||
p := reg.ContinuityManager()
|
||||
cl := newClient()
|
||||
|
||||
t.Run("case=continue cookie resets when signature is invalid", func(t *testing.T) {
|
||||
ts := newServer(t, p, new(persisterTestCase))
|
||||
href := ts.URL + "/" + x.NewUUID().String()
|
||||
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
req := testhelpers.NewTestHTTPRequest(t, "GET", href, nil)
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
for _, c := range res.Cookies() {
|
||||
// Change something in the string
|
||||
c.Value = strings.Replace(c.Value, "a", "b", 1)
|
||||
req.AddCookie(c)
|
||||
}
|
||||
res, err = testhelpers.NewTestClient(t).Do(req)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable().ReasonField)
|
||||
|
||||
require.Len(t, res.Cookies(), 1, "continuing the flow with a broken cookie should instruct the browser to forget it")
|
||||
assert.EqualValues(t, res.Cookies()[0].Name, continuity.CookieName)
|
||||
})
|
||||
|
||||
t.Run("case=can deal with duplicate cookies", func(t *testing.T) {
|
||||
tc := &persisterTestCase{expected: &persisterTestPayload{"bar"}}
|
||||
ts := newServer(t, p, tc)
|
||||
href := ts.URL + "/" + x.NewUUID().String()
|
||||
|
||||
res, err := testhelpers.NewTestClient(t).Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
// We change the key to another one
|
||||
href = ts.URL + "/" + x.NewUUID().String()
|
||||
req := testhelpers.NewTestHTTPRequest(t, "GET", href, nil)
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
for _, c := range res.Cookies() {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
|
||||
tc.ro = []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"})}
|
||||
res, err = testhelpers.NewTestClient(t).Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
for _, c := range res.Cookies() {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
|
||||
res, err = testhelpers.NewTestClient(t).Do(req)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
require.Len(t, res.Cookies(), 1, "continuing the flow with a broken cookie should instruct the browser to forget it")
|
||||
assert.EqualValues(t, res.Cookies()[0].Name, continuity.CookieName)
|
||||
|
||||
var b bytes.Buffer
|
||||
require.NoError(t, json.NewEncoder(&b).Encode(tc.expected))
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body)
|
||||
assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body)
|
||||
})
|
||||
|
||||
t.Run("case=pause and use session with expiry", func(t *testing.T) {
|
||||
cl := newClient()
|
||||
|
||||
tc := &persisterTestCase{
|
||||
ro: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"}), continuity.WithExpireInsteadOfDelete(time.Minute)},
|
||||
wo: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{}), continuity.WithExpireInsteadOfDelete(time.Minute)},
|
||||
}
|
||||
ts := newServer(t, p, tc)
|
||||
genid := func() string {
|
||||
return ts.URL + "/" + x.NewUUID().String()
|
||||
}
|
||||
|
||||
href := genid()
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
tc.ro = []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"}), continuity.WithExpireInsteadOfDelete(-time.Minute)}
|
||||
tc.wo = []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{""}), continuity.WithExpireInsteadOfDelete(-time.Minute)}
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
require.NoError(t, res.Body.Close())
|
||||
assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable().ReasonField)
|
||||
})
|
||||
|
||||
for k, tc := range []persisterTestCase{
|
||||
{},
|
||||
{
|
||||
ro: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{"bar"})},
|
||||
wo: []continuity.ManagerOption{continuity.WithPayload(&persisterTestPayload{})},
|
||||
expected: &persisterTestPayload{"bar"},
|
||||
},
|
||||
{
|
||||
ro: []continuity.ManagerOption{continuity.WithIdentity(i)},
|
||||
wo: []continuity.ManagerOption{continuity.WithIdentity(i)},
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
|
||||
cl := newClient()
|
||||
ts := newServer(t, p, &tc)
|
||||
genid := func() string {
|
||||
return ts.URL + "/" + x.NewUUID().String()
|
||||
}
|
||||
|
||||
t.Run("case=resume non-existing session", func(t *testing.T) {
|
||||
href := genid()
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable().ReasonField)
|
||||
})
|
||||
|
||||
t.Run("case=pause and resume session", func(t *testing.T) {
|
||||
href := genid()
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
if tc.expectedErr != nil {
|
||||
require.Equal(t, http.StatusGone, res.StatusCode, "%s", body)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, "%s", body)
|
||||
|
||||
var b bytes.Buffer
|
||||
require.NoError(t, json.NewEncoder(&b).Encode(tc.expected))
|
||||
assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body)
|
||||
assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body)
|
||||
})
|
||||
|
||||
t.Run("case=pause and retry session", func(t *testing.T) {
|
||||
href := genid()
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable().ReasonField)
|
||||
})
|
||||
|
||||
t.Run("case=pause and resume session in the same request", func(t *testing.T) {
|
||||
href := genid()
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "POST", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
var b bytes.Buffer
|
||||
require.NoError(t, json.NewEncoder(&b).Encode(tc.expected))
|
||||
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body)
|
||||
assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body)
|
||||
})
|
||||
|
||||
t.Run("case=pause, abort, and continue session with failure", func(t *testing.T) {
|
||||
href := genid()
|
||||
res, err := cl.Do(testhelpers.NewTestHTTPRequest(t, "PUT", href, nil))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "DELETE", href, nil))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
res, err = cl.Do(testhelpers.NewTestHTTPRequest(t, "GET", href, nil))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, res.Body.Close()) })
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
body := ioutilx.MustReadAll(res.Body)
|
||||
assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), continuity.ErrNotResumable().ReasonField)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user