feat: user v3 contact email and phone (#8644)

# Which Problems Are Solved

Endpoints to maintain email and phone contact on user v3 are not
implemented.

# How the Problems Are Solved

Add 3 endpoints with SetContactEmail, VerifyContactEmail and
ResendContactEmailCode.
Add 3 endpoints with SetContactPhone, VerifyContactPhone and
ResendContactPhoneCode.
Refactor the logic how contact is managed in the user creation and
update.

# Additional Changes

None

# Additional Context

- part of https://github.com/zitadel/zitadel/issues/6433

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2024-09-25 15:31:31 +02:00
committed by GitHub
parent 624fee97c0
commit 62cdec222e
21 changed files with 4924 additions and 432 deletions

View File

@@ -0,0 +1,772 @@
//go:build integration
package user_test
import (
"context"
"testing"
"github.com/brianvoe/gofakeit/v6"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
)
func TestServer_SetContactEmail(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
schema := []byte(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`)
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
type res struct {
want *resource_object.Details
returnCode bool
}
tests := []struct {
name string
ctx context.Context
dep func(req *user.SetContactEmailRequest) error
req *user.SetContactEmailRequest
res res
wantErr bool
}{
{
name: "email patch, no context",
ctx: context.Background(),
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
},
},
wantErr: true,
},
{
name: "email patch, no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
},
},
wantErr: true,
},
{
name: "email patch, not found",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Id: "notexisting",
Email: &user.SetEmail{
Address: gofakeit.Email(),
},
},
wantErr: true,
},
{
name: "email patch, not found, org",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: "not existing",
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
},
},
wantErr: true,
},
{
name: "email patch, empty",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
email := gofakeit.Email()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, email)
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{},
},
wantErr: true,
},
{
name: "email patch, no change",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
email := gofakeit.Email()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, email)
req.Email.Address = email
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "email patch, no org, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Email: &user.SetEmail{
Address: gofakeit.Email(),
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "email patch, return, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
Verification: &user.SetEmail_ReturnCode{},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
returnCode: true,
},
},
{
name: "email patch, return, invalid template",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
Verification: &user.SetEmail_SendCode{SendCode: &user.SendEmailVerificationCode{UrlTemplate: gu.Ptr("{{")}},
},
},
wantErr: true,
},
{
name: "email patch, verified, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
Verification: &user.SetEmail_IsVerified{IsVerified: true},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "email patch, template, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
Verification: &user.SetEmail_SendCode{SendCode: &user.SendEmailVerificationCode{UrlTemplate: gu.Ptr("https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}")}},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "email patch, sent, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Email: &user.SetEmail{
Address: gofakeit.Email(),
Verification: &user.SetEmail_SendCode{},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.dep != nil {
err := tt.dep(tt.req)
require.NoError(t, err)
}
got, err := instance.Client.UserV3Alpha.SetContactEmail(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
if tt.res.returnCode {
assert.NotNil(t, got.VerificationCode)
} else {
assert.Nil(t, got.VerificationCode)
}
})
}
}
func TestServer_VerifyContactEmail(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
schema := []byte(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`)
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
type res struct {
want *resource_object.Details
}
tests := []struct {
name string
ctx context.Context
dep func(req *user.VerifyContactEmailRequest) error
req *user.VerifyContactEmailRequest
res res
wantErr bool
}{
{
name: "email verify, no context",
ctx: context.Background(),
dep: func(req *user.VerifyContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "email verify, no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
dep: func(req *user.VerifyContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "email verify, not found",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactEmailRequest) error {
return nil
},
req: &user.VerifyContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Id: "notexisting",
VerificationCode: "unimportant",
},
wantErr: true,
},
{
name: "email verify, not found, org",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: "not existing",
},
},
},
wantErr: true,
},
{
name: "email verify, wrong code",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactEmailRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.VerifyContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
VerificationCode: "wrong",
},
wantErr: true,
},
{
name: "email verify, no org, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactEmailRequest{},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "email verify, return, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactEmailRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactEmailRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.dep != nil {
err := tt.dep(tt.req)
require.NoError(t, err)
}
got, err := instance.Client.UserV3Alpha.VerifyContactEmail(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
})
}
}
func TestServer_ResendContactEmailCode(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
schema := []byte(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`)
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
type res struct {
want *resource_object.Details
returnCode bool
}
tests := []struct {
name string
ctx context.Context
dep func(req *user.ResendContactEmailCodeRequest) error
req *user.ResendContactEmailCodeRequest
res res
wantErr bool
}{
{
name: "email resend, no context",
ctx: context.Background(),
dep: func(req *user.ResendContactEmailCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "email resend, no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
dep: func(req *user.ResendContactEmailCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "email resend, not found",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactEmailCodeRequest) error {
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Id: "notexisting",
},
wantErr: true,
},
{
name: "email resend, not found, org",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactEmailCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: "not existing",
},
},
},
wantErr: true,
},
{
name: "email resend, no code",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactEmailCodeRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "email resend, no org, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactEmailCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.ResendContactEmailCodeRequest{},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "email resend, return, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactEmailCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Verification: &user.ResendContactEmailCodeRequest_ReturnCode{},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
returnCode: true,
},
},
{
name: "email resend, sent, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactEmailCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
return nil
},
req: &user.ResendContactEmailCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Verification: &user.ResendContactEmailCodeRequest_SendCode{},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.dep != nil {
err := tt.dep(tt.req)
require.NoError(t, err)
}
got, err := instance.Client.UserV3Alpha.ResendContactEmailCode(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
if tt.res.returnCode {
assert.NotNil(t, got.VerificationCode)
} else {
assert.Nil(t, got.VerificationCode)
}
})
}
}

View File

@@ -0,0 +1,701 @@
//go:build integration
package user_test
import (
"context"
"testing"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
)
func TestServer_SetContactPhone(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
schema := []byte(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`)
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
type res struct {
want *resource_object.Details
returnCode bool
}
tests := []struct {
name string
ctx context.Context
dep func(req *user.SetContactPhoneRequest) error
req *user.SetContactPhoneRequest
res res
wantErr bool
}{
{
name: "phone patch, no context",
ctx: context.Background(),
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
},
},
wantErr: true,
},
{
name: "phone patch, no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
},
},
wantErr: true,
},
{
name: "phone patch, not found",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Id: "notexisting",
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
},
},
wantErr: true,
},
{
name: "phone patch, not found, org",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: "not existing",
},
},
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
},
},
wantErr: true,
},
{
name: "phone patch, no change",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
number := gofakeit.Phone()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, number)
req.Phone.Number = number
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Phone: &user.SetPhone{},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "phone patch, no org, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "phone patch, return, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
Verification: &user.SetPhone_ReturnCode{},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
returnCode: true,
},
},
{
name: "phone patch, verified, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
Verification: &user.SetPhone_IsVerified{IsVerified: true},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "phone patch, sent, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.SetContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.SetContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Phone: &user.SetPhone{
Number: gofakeit.Phone(),
Verification: &user.SetPhone_SendCode{},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.dep != nil {
err := tt.dep(tt.req)
require.NoError(t, err)
}
got, err := instance.Client.UserV3Alpha.SetContactPhone(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
if tt.res.returnCode {
assert.NotNil(t, got.VerificationCode)
} else {
assert.Nil(t, got.VerificationCode)
}
})
}
}
func TestServer_VerifyContactPhone(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
schema := []byte(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`)
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
type res struct {
want *resource_object.Details
returnCodePhone bool
}
tests := []struct {
name string
ctx context.Context
dep func(req *user.VerifyContactPhoneRequest) error
req *user.VerifyContactPhoneRequest
res res
wantErr bool
}{
{
name: "phone verify, no context",
ctx: context.Background(),
dep: func(req *user.VerifyContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "phone verify, no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
dep: func(req *user.VerifyContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "phone verify, not found",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactPhoneRequest) error {
return nil
},
req: &user.VerifyContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Id: "notexisting",
VerificationCode: "unimportant",
},
wantErr: true,
},
{
name: "phone verify, not found, org",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: "not existing",
},
},
},
wantErr: true,
},
{
name: "phone verify, wrong code",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactPhoneRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.VerifyContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
VerificationCode: "wrong",
},
wantErr: true,
},
{
name: "phone verify, no org, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactPhoneRequest{},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "phone verify, return, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.VerifyContactPhoneRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
req.VerificationCode = verifyResp.GetVerificationCode()
return nil
},
req: &user.VerifyContactPhoneRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
returnCodePhone: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.dep != nil {
err := tt.dep(tt.req)
require.NoError(t, err)
}
got, err := instance.Client.UserV3Alpha.VerifyContactPhone(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
})
}
}
func TestServer_ResendContactPhoneCode(t *testing.T) {
t.Parallel()
instance := integration.NewInstance(CTX)
ensureFeatureEnabled(t, instance)
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
schema := []byte(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}`)
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
type res struct {
want *resource_object.Details
returnCode bool
}
tests := []struct {
name string
ctx context.Context
dep func(req *user.ResendContactPhoneCodeRequest) error
req *user.ResendContactPhoneCodeRequest
res res
wantErr bool
}{
{
name: "phone resend, no context",
ctx: context.Background(),
dep: func(req *user.ResendContactPhoneCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "phone resend, no permission",
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
dep: func(req *user.ResendContactPhoneCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "phone resend, not found",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactPhoneCodeRequest) error {
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Id: "notexisting",
},
wantErr: true,
},
{
name: "phone resend, not found, org",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactPhoneCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: "not existing",
},
},
},
wantErr: true,
},
{
name: "phone resend, no code",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactPhoneCodeRequest) error {
data := "{\"name\": \"user\"}"
schemaID := schemaResp.GetDetails().GetId()
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
req.Id = userResp.GetDetails().GetId()
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
},
wantErr: true,
},
{
name: "phone resend, no org, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactPhoneCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.ResendContactPhoneCodeRequest{},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
{
name: "phone resend, return, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactPhoneCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Verification: &user.ResendContactPhoneCodeRequest_ReturnCode{},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
returnCode: true,
},
},
{
name: "phone resend, sent, ok",
ctx: isolatedIAMOwnerCTX,
dep: func(req *user.ResendContactPhoneCodeRequest) error {
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
req.Id = userResp.GetDetails().GetId()
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
return nil
},
req: &user.ResendContactPhoneCodeRequest{
Organization: &object.Organization{
Property: &object.Organization_OrgId{
OrgId: orgResp.GetOrganizationId(),
},
},
Verification: &user.ResendContactPhoneCodeRequest_SendCode{},
},
res: res{
want: &resource_object.Details{
Changed: timestamppb.Now(),
Owner: &object.Owner{
Type: object.OwnerType_OWNER_TYPE_ORG,
Id: orgResp.GetOrganizationId(),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.dep != nil {
err := tt.dep(tt.req)
require.NoError(t, err)
}
got, err := instance.Client.UserV3Alpha.ResendContactPhoneCode(tt.ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
if tt.res.returnCode {
assert.NotNil(t, got.VerificationCode)
} else {
assert.Nil(t, got.VerificationCode)
}
})
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
)
var (
@@ -51,7 +52,7 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
require.NoError(ttt, err)
assert.NoError(ttt, err)
if f.UserSchema.GetEnabled() {
return
}
@@ -59,4 +60,13 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
retryDuration,
time.Second,
"timed out waiting for ensuring instance feature")
require.EventuallyWithT(t,
func(ttt *assert.CollectT) {
_, err := instance.Client.UserV3Alpha.SearchUsers(ctx, &user.SearchUsersRequest{})
assert.NoError(ttt, err)
},
retryDuration,
time.Second,
"timed out waiting for ensuring instance feature call")
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/brianvoe/gofakeit/v6"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/logging"
"google.golang.org/protobuf/types/known/structpb"
@@ -628,16 +629,20 @@ func TestServer_PatchUser(t *testing.T) {
}
got, err := instance.Client.UserV3Alpha.PatchUser(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.res.want, got.Details)
if tt.res.returnCodeEmail {
require.NotNil(t, got.EmailCode)
assert.NotNil(t, got.EmailCode)
} else {
assert.Nil(t, got.EmailCode)
}
if tt.res.returnCodePhone {
require.NotNil(t, got.PhoneCode)
assert.NotNil(t, got.PhoneCode)
} else {
assert.Nil(t, got.PhoneCode)
}
})
}
@@ -843,10 +848,10 @@ func TestServer_DeleteUser(t *testing.T) {
}
got, err := instance.Client.UserV3Alpha.DeleteUser(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
@@ -1054,10 +1059,10 @@ func TestServer_LockUser(t *testing.T) {
}
got, err := instance.Client.UserV3Alpha.LockUser(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
@@ -1237,10 +1242,10 @@ func TestServer_UnlockUser(t *testing.T) {
}
got, err := instance.Client.UserV3Alpha.UnlockUser(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
@@ -1439,10 +1444,10 @@ func TestServer_DeactivateUser(t *testing.T) {
}
got, err := instance.Client.UserV3Alpha.DeactivateUser(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}
@@ -1622,10 +1627,10 @@ func TestServer_ActivateUser(t *testing.T) {
}
got, err := instance.Client.UserV3Alpha.ActivateUser(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.NoError(t, err)
integration.AssertResourceDetails(t, tt.want, got.Details)
})
}