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:
472
persistence/sql/migratest/migration_test.go
Normal file
472
persistence/sql/migratest/migration_test.go
Normal file
@@ -0,0 +1,472 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package migratest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bradleyjkemp/cupaloy/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/persistence/sql"
|
||||
gomigrations "github.com/ory/kratos/persistence/sql/migrations/go"
|
||||
"github.com/ory/kratos/selfservice/flow/login"
|
||||
"github.com/ory/kratos/selfservice/flow/recovery"
|
||||
"github.com/ory/kratos/selfservice/flow/registration"
|
||||
"github.com/ory/kratos/selfservice/flow/settings"
|
||||
"github.com/ory/kratos/selfservice/flow/verification"
|
||||
"github.com/ory/kratos/selfservice/strategy/code"
|
||||
"github.com/ory/kratos/selfservice/strategy/link"
|
||||
"github.com/ory/kratos/session"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/pop/v6"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/dbal"
|
||||
"github.com/ory/x/fsx"
|
||||
"github.com/ory/x/logrusx"
|
||||
"github.com/ory/x/migratest"
|
||||
"github.com/ory/x/networkx"
|
||||
"github.com/ory/x/pagination/keysetpagination"
|
||||
"github.com/ory/x/popx"
|
||||
"github.com/ory/x/sqlcon"
|
||||
"github.com/ory/x/sqlcon/dockertest"
|
||||
)
|
||||
|
||||
func snapshotFor(paths ...string) *cupaloy.Config {
|
||||
return cupaloy.New(
|
||||
cupaloy.CreateNewAutomatically(true),
|
||||
cupaloy.FailOnUpdate(true),
|
||||
cupaloy.SnapshotFileExtension(".json"),
|
||||
cupaloy.SnapshotSubdirectory(filepath.Join(paths...)),
|
||||
)
|
||||
}
|
||||
|
||||
func CompareWithFixture(t *testing.T, actual interface{}, prefix string, id string) {
|
||||
s := snapshotFor("fixtures", prefix)
|
||||
actualJSON, err := json.MarshalIndent(actual, "", " ")
|
||||
require.NoError(t, err)
|
||||
err = s.SnapshotWithName(id, actualJSON)
|
||||
assert.NoErrorf(t, err, "actual = %s", string(actualJSON))
|
||||
}
|
||||
|
||||
func TestMigrations_SQLite(t *testing.T) {
|
||||
t.Parallel()
|
||||
sqlite, err := pop.NewConnection(&pop.ConnectionDetails{
|
||||
URL: dbal.NewSQLiteTestDatabase(t),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqlite.Open())
|
||||
|
||||
testDatabase(t, "sqlite", sqlite)
|
||||
}
|
||||
|
||||
func TestMigrations_Postgres(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping testing in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
testDatabase(t, "postgres", dockertest.ConnectPop(t, dockertest.RunTestPostgreSQLWithVersion(t, "16")))
|
||||
}
|
||||
|
||||
func TestMigrations_Mysql(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping testing in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
testDatabase(t, "mysql", dockertest.ConnectPop(t, dockertest.RunTestMySQLWithVersion(t, "8.4")))
|
||||
}
|
||||
|
||||
func TestMigrations_Cockroach(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping testing in short mode")
|
||||
}
|
||||
t.Parallel()
|
||||
testDatabase(t, "cockroach", dockertest.ConnectPop(t, dockertest.RunTestCockroachDBWithVersion(t, "latest-v25.4")))
|
||||
}
|
||||
|
||||
func testDatabase(t *testing.T, db string, c *pop.Connection) {
|
||||
ctx := context.Background()
|
||||
l := logrusx.New("", "", logrusx.ForceLevel(logrus.DebugLevel))
|
||||
|
||||
url := c.URL()
|
||||
// workaround for https://github.com/ory/pop/issues/538
|
||||
switch db {
|
||||
case "mysql":
|
||||
url = "mysql://" + url
|
||||
case "sqlite":
|
||||
url = "sqlite3://" + url
|
||||
case "cockroach":
|
||||
url = "cockroach" + strings.TrimPrefix(url, "postgres")
|
||||
}
|
||||
if db != "sqlite" {
|
||||
dbName := "testdb" + strings.ReplaceAll(x.NewUUID().String(), "-", "")
|
||||
require.NoError(t, c.RawQuery("CREATE DATABASE "+dbName).Exec())
|
||||
url = regexp.MustCompile(`/[a-z0-9]+\?`).ReplaceAllString(url, "/"+dbName+"?")
|
||||
}
|
||||
|
||||
t.Logf("URL: %s", url)
|
||||
var err error
|
||||
c, err = pop.NewConnection(&pop.ConnectionDetails{URL: url})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Open())
|
||||
|
||||
tm, err := popx.NewMigrationBox(
|
||||
fsx.Merge(sql.Migrations, networkx.Migrations),
|
||||
c, l,
|
||||
popx.WithGoMigrations(gomigrations.All),
|
||||
popx.WithTestdata(t, os.DirFS("./testdata")),
|
||||
popx.WithoutGoldenDatabase(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tm.Up(ctx) // for easy breakpointing
|
||||
// _ = tm.DumpMigrationSchema(ctx) // uncomment to get the current state of the database after migrations have run
|
||||
if !assert.NoError(t, err) {
|
||||
assert.NoError(t, tm.DumpMigrationSchema(ctx))
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
opts := driver.WithConfigOptions(
|
||||
configx.WithValues(map[string]any{
|
||||
config.ViperKeyDSN: url,
|
||||
config.ViperKeyPublicBaseURL: "https://www.ory.sh/",
|
||||
config.ViperKeyIdentitySchemas: config.Schemas{{ID: "default", URL: "file://stub/default.schema.json"}},
|
||||
config.ViperKeySecretsDefault: []string{"secret"},
|
||||
}),
|
||||
configx.SkipValidation(),
|
||||
)
|
||||
|
||||
t.Run("suite=fixtures", func(t *testing.T) {
|
||||
t.Run("case=identity", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
ids, _, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ListIdentityParameters{Expand: identity.ExpandEverything, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(1000)}})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
var found []string
|
||||
for y, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual := &ids[y]
|
||||
|
||||
for _, a := range actual.VerifiableAddresses {
|
||||
CompareWithFixture(t, a, "identity_verification_address", a.ID.String())
|
||||
}
|
||||
|
||||
for _, a := range actual.RecoveryAddresses {
|
||||
CompareWithFixture(t, a, "identity_recovery_address", a.ID.String())
|
||||
}
|
||||
|
||||
CompareWithFixture(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), "identity", id.ID.String())
|
||||
}
|
||||
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "identity"), found)
|
||||
})
|
||||
|
||||
t.Run("case=identity_get", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
ids, _, err := d.PrivilegedIdentityPool().ListIdentities(context.Background(), identity.ListIdentityParameters{Expand: identity.ExpandNothing, KeySetPagination: []keysetpagination.Option{keysetpagination.WithSize(1000)}})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
actual, err := d.PrivilegedIdentityPool().GetIdentityConfidential(context.Background(), id.ID)
|
||||
require.NoError(t, err)
|
||||
found = append(found, actual.ID.String())
|
||||
|
||||
CompareWithFixture(t, identity.WithCredentialsAndAdminMetadataInJSON(*actual), "identity", id.ID.String())
|
||||
}
|
||||
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "identity"), found)
|
||||
})
|
||||
|
||||
t.Run("case=verification_token", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []link.VerificationToken
|
||||
|
||||
require.NoError(t, c.All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
for _, id := range ids {
|
||||
CompareWithFixture(t, id, "verification_token", id.ID.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=session", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ids []session.Session
|
||||
require.NoError(t, c.Select("id").All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual, err := d.SessionPersister().GetSession(context.Background(), id.ID, session.ExpandEverything)
|
||||
require.NoErrorf(t, err, "Trying to get session: %s", id.ID)
|
||||
require.NotEmpty(t, actual.LogoutToken, "check if migrations have generated a logout token for existing sessions")
|
||||
CompareWithFixture(t, actual, "session", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "session"), found)
|
||||
})
|
||||
|
||||
t.Run("case=login", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []login.Flow
|
||||
require.NoError(t, c.Select("id").All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual, err := d.LoginFlowPersister().GetLoginFlow(context.Background(), id.ID)
|
||||
require.NoError(t, err)
|
||||
CompareWithFixture(t, actual, "login_flow", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "login_flow"), found)
|
||||
})
|
||||
|
||||
t.Run("case=registration", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []registration.Flow
|
||||
require.NoError(t, c.Select("id").All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual, err := d.RegistrationFlowPersister().GetRegistrationFlow(context.Background(), id.ID)
|
||||
require.NoError(t, err)
|
||||
CompareWithFixture(t, actual, "registration_flow", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "registration_flow"), found)
|
||||
})
|
||||
|
||||
t.Run("case=settings_flow", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []settings.Flow
|
||||
require.NoError(t, c.Select("id").All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual, err := d.SettingsFlowPersister().GetSettingsFlow(context.Background(), id.ID)
|
||||
require.NoError(t, err, id.ID.String())
|
||||
CompareWithFixture(t, actual, "settings_flow", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "settings_flow"), found)
|
||||
})
|
||||
|
||||
t.Run("case=recovery_flow", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []recovery.Flow
|
||||
require.NoError(t, c.Select("id").All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual, err := d.RecoveryFlowPersister().GetRecoveryFlow(context.Background(), id.ID)
|
||||
require.NoError(t, err)
|
||||
CompareWithFixture(t, actual, "recovery_flow", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_flow"), found)
|
||||
})
|
||||
|
||||
t.Run("case=verification_flow", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []verification.Flow
|
||||
require.NoError(t, c.Select("id").All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
actual, err := d.VerificationFlowPersister().GetVerificationFlow(context.Background(), id.ID)
|
||||
require.NoError(t, err)
|
||||
CompareWithFixture(t, actual, "verification_flow", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "verification_flow"), found)
|
||||
})
|
||||
|
||||
t.Run("case=recovery_token", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []link.RecoveryToken
|
||||
require.NoError(t, c.All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
CompareWithFixture(t, id, "recovery_token", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_token"), found)
|
||||
})
|
||||
|
||||
t.Run("case=recovery_code", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []code.RecoveryCode
|
||||
require.NoError(t, c.All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
CompareWithFixture(t, id, "recovery_code", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_code"), found)
|
||||
})
|
||||
|
||||
t.Run("case=registration_code", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []code.RegistrationCode
|
||||
require.NoError(t, c.All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
CompareWithFixture(t, id, "registration_code", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "registration_code"), found)
|
||||
})
|
||||
|
||||
t.Run("case=login_code", func(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var ids []code.LoginCode
|
||||
require.NoError(t, c.All(&ids))
|
||||
require.NotEmpty(t, ids)
|
||||
|
||||
var found []string
|
||||
for _, id := range ids {
|
||||
found = append(found, id.ID.String())
|
||||
CompareWithFixture(t, id, "login_code", id.ID.String())
|
||||
}
|
||||
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "login_code"), found)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("suite=constraints", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
// clean up test duplicates - remove identity_credential_identifiers 10985ed1-5b6e-4012-ac10-03d87df65618 - otherwise down migration later fails.
|
||||
require.NoError(t, c.RawQuery("DELETE FROM identity_credential_identifiers WHERE identifier = '10985ed1-5b6e-4012-ac10-03d87df65618'").Exec())
|
||||
})
|
||||
|
||||
d, err := driver.New(
|
||||
context.Background(),
|
||||
os.Stderr,
|
||||
opts,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
sr, err := d.SettingsFlowPersister().GetSettingsFlow(context.Background(), x.ParseUUID("a79bfcf1-68ae-49de-8b23-4f96921b8341"))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, d.PrivilegedIdentityPool().DeleteIdentity(context.Background(), sr.IdentityID))
|
||||
|
||||
_, err = d.SettingsFlowPersister().GetSettingsFlow(context.Background(), x.ParseUUID("a79bfcf1-68ae-49de-8b23-4f96921b8341"))
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, sqlcon.ErrNoRows())
|
||||
})
|
||||
|
||||
t.Run("suite=down", func(t *testing.T) {
|
||||
err = tm.Down(ctx, -1) // for easy breakpointing
|
||||
if !assert.NoError(t, err) {
|
||||
assert.NoError(t, tm.DumpMigrationSchema(ctx))
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user