Files
zitadel/backend/v3/storage/database/change_test.go
T
5df8a97f9d feat(repository): implement user repository (#10967)
## Summary

This PR introduces the relational user repository for backend v3,
including schema, repository APIs, reducers/projections, and tests for
both human and machine users.

## What changed

* Added user-focused relational migrations (users, verifications,
metadata, PATs, machine keys, passkeys, identity provider links) and
related FK wiring.
* Implemented/expanded repository domain contracts and storage
implementations for human and machine user flows.
* Added support for machine secret handling and aligned access token
type modeling.
* Extended user domain models (verifications, password/email/phone/TOTP
state, passkeys, metadata, invite handling, identity provider links).
* Updated projection/reduction coverage and integration tests for key
user lifecycle events.
* Regenerated and updated mocks/enums plus supporting database
change/column utilities.

## Notes for reviewers

* This PR is large because it establishes the foundational relational
user storage layer and related test scaffolding. Sorry for that.

## Closes

Closes #10218
Closes #10224
Closes #10225
Closes #10209
Closes #10802

---------

Co-authored-by: Iraq Jaber <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Marco A. <[email protected]>
Co-authored-by: Tim Möhlmann <[email protected]>
2026-02-23 16:03:47 +01:00

269 lines
7.0 KiB
Go

package database
import (
"testing"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestChangeWrite(t *testing.T) {
type want struct {
stmt string
args []any
}
for _, test := range []struct {
name string
change Change
want want
}{
{
name: "change",
change: NewChange(NewColumn("table", "column"), "value"),
want: want{
stmt: "column = $1",
args: []any{"value"},
},
},
{
name: "change ptr to null",
change: NewChangePtr[int](NewColumn("table", "column"), nil),
want: want{
stmt: "column = NULL",
args: nil,
},
},
{
name: "change ptr to value",
change: NewChangePtr(NewColumn("table", "column"), gu.Ptr(42)),
want: want{
stmt: "column = $1",
args: []any{42},
},
},
{
name: "change to NULL",
change: NewChangeToNull(NewColumn("table", "column")),
want: want{
stmt: "column = NULL",
args: nil,
},
},
{
name: "multiple changes",
change: NewChanges(
NewChange(NewColumn("table", "column1"), "value1"),
NewChangePtr[int](NewColumn("table", "column2"), nil),
NewChange(NewColumn("table", "column3"), 123),
NewChangeToNull(NewColumn("table", "column4")),
),
want: want{
stmt: "column1 = $1, column2 = NULL, column3 = $2, column4 = NULL",
args: []any{"value1", 123},
},
},
{
name: "increment change",
change: NewIncrementColumnChange(NewColumn("table", "counter"), NewColumn("table", "counter")),
want: want{
stmt: "counter = table.counter + 1",
},
},
} {
t.Run(test.name, func(t *testing.T) {
var builder StatementBuilder
err := test.change.Write(&builder)
require.NoError(t, err)
assert.Equal(t, test.want.stmt, builder.String())
require.Len(t, builder.Args(), len(test.want.args))
for i, arg := range test.want.args {
assert.Equal(t, arg, builder.Args()[i])
}
})
}
}
func TestChangeToStatement(t *testing.T) {
type want struct {
stmt string
args []any
}
for _, test := range []struct {
name string
prefillBuilder func(builder *StatementBuilder)
change *changeToStatement
want want
}{
{
name: "change to statement",
change: NewChangeToStatement(NewColumn("table", "column"), func(builder *StatementBuilder) {
builder.WriteString("SELECT 1")
}).(*changeToStatement),
want: want{
stmt: "column = (SELECT 1)",
args: nil,
},
},
{
name: "change to statement with args",
change: NewChangeToStatement(NewColumn("table", "column"), func(builder *StatementBuilder) {
builder.WriteString("SELECT ")
builder.WriteArg(42)
}).(*changeToStatement),
want: want{
stmt: "column = (SELECT $1)",
args: []any{42},
},
},
{
name: "change to statement with existing builder args",
prefillBuilder: func(builder *StatementBuilder) {
builder.WriteString("UPDATE table SET ")
assert.NoError(t, NewChanges(
NewChange(NewColumn("table", "field1"), "asdf"),
NewChangeToNull(NewColumn("table", "field2")),
NewChangeToColumn(NewColumn("table", "field3"), NewColumn("table", "field4")),
).Write(builder))
builder.WriteString(", ")
},
change: NewChangeToStatement(NewColumn("table", "column"), func(builder *StatementBuilder) {
builder.WriteString("SELECT ")
builder.WriteArg(42)
builder.WriteString(" FROM other_table WHERE ")
NewBooleanCondition(NewColumn("table", "id"), true).Write(builder)
}).(*changeToStatement),
want: want{
stmt: "UPDATE table SET field1 = $1, field2 = NULL, field3 = table.field4, column = (SELECT $2 FROM other_table WHERE table.id = $3)",
args: []any{"asdf", 42, true},
},
},
} {
t.Run(test.name, func(t *testing.T) {
var builder StatementBuilder
if test.prefillBuilder != nil {
test.prefillBuilder(&builder)
}
assert.NoError(t, test.change.Write(&builder))
assert.Equal(t, test.want.stmt, builder.String())
assert.Equal(t, builder.Args(), test.want.args)
})
}
}
func TestCTEChange(t *testing.T) {
type want struct {
stmt string
args []any
}
for _, test := range []struct {
name string
prefillBuilder func(builder *StatementBuilder)
afterCTE func(builder *StatementBuilder)
change *cteChange
want want
}{
{
name: "only CTE change",
change: NewCTEChange(
func(builder *StatementBuilder) {
builder.WriteString("SELECT 1 AS value")
},
nil,
).(*cteChange),
want: want{
stmt: "WITH cte AS (SELECT 1 AS value) ",
args: nil,
},
},
{
name: "with existing CTE",
prefillBuilder: func(builder *StatementBuilder) {
builder.WriteString("existing_cte AS (SELECT ")
builder.WriteArg(42)
builder.WriteString(" AS reason), ")
},
change: NewCTEChange(
func(builder *StatementBuilder) {
builder.WriteString("SELECT ")
Columns{
NewColumn("table", "column1"),
NewColumn("table", "column2"),
}.WriteQualified(builder)
builder.WriteString(", ")
builder.WriteArgs("asdf", 123, false, NullInstruction)
},
nil,
).(*cteChange),
want: want{
stmt: "WITH existing_cte AS (SELECT $1 AS reason), cte AS (SELECT table.column1, table.column2, $2, $3, $4, NULL) ",
args: []any{42, "asdf", 123, false},
},
},
{
name: "CTE change with after CTE statements",
change: NewCTEChange(
func(builder *StatementBuilder) {
builder.WriteString("SELECT ")
builder.WriteArg(1)
builder.WriteString(" AS value")
},
nil,
).(*cteChange),
afterCTE: func(builder *StatementBuilder) {
builder.WriteString("SELECT * FROM cte ")
},
want: want{
stmt: "WITH cte AS (SELECT $1 AS value) SELECT * FROM cte ",
args: []any{1},
},
},
{
name: "CTE change with column change after CTE",
change: NewCTEChange(
func(builder *StatementBuilder) {
builder.WriteString("SELECT ")
builder.WriteArg(1)
builder.WriteString(" AS value")
},
func(name string) Change {
return NewChangeToStatement(NewColumn("table", "field"), func(builder *StatementBuilder) {
builder.WriteString("SELECT * FROM ")
builder.WriteString(name)
builder.WriteString(" WHERE ")
NewColumnCondition(NewColumn(name, "value"), NewColumn("table", "value")).Write(builder)
})
},
).(*cteChange),
afterCTE: func(builder *StatementBuilder) {
builder.WriteString("UPDATE test SET ")
},
want: want{
stmt: "WITH cte AS (SELECT $1 AS value) UPDATE test SET field = (SELECT * FROM cte WHERE cte.value = table.value)",
args: []any{1},
},
},
} {
t.Run(test.name, func(t *testing.T) {
var builder StatementBuilder
builder.WriteString("WITH ")
if test.prefillBuilder != nil {
test.prefillBuilder(&builder)
}
builder.WriteString("cte AS (")
test.change.SetName("cte")
test.change.WriteCTE(&builder)
builder.WriteString(") ")
if test.afterCTE != nil {
test.afterCTE(&builder)
}
assert.NoError(t, test.change.Write(&builder))
assert.Equal(t, test.want.stmt, builder.String())
assert.Equal(t, builder.Args(), test.want.args)
})
}
}