mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 11:34:26 +00:00
dd33538c0a
* feat: return 404 or 409 if org reg disallowed * fix: system limit permissions * feat: add iam limits api * feat: disallow public org registrations on default instance * add integration test * test: integration * fix test * docs: describe public org registrations * avoid updating docs deps * fix system limits integration test * silence integration tests * fix linting * ignore strange linter complaints * review * improve reset properties naming * redefine the api * use restrictions aggregate * test query * simplify and test projection * test commands * fix unit tests * move integration test * support restrictions on default instance * also test GetRestrictions * self review * lint * abstract away resource owner * fix tests * configure supported languages * fix allowed languages * fix tests * default lang must not be restricted * preferred language must be allowed * change preferred languages * check languages everywhere * lint * test command side * lint * add integration test * add integration test * restrict supported ui locales * lint * lint * cleanup * lint * allow undefined preferred language * fix integration tests * update main * fix env var * ignore linter * ignore linter * improve integration test config * reduce cognitive complexity * compile * check for duplicates * remove useless restriction checks * review * revert restriction renaming * fix language restrictions * lint * generate * allow custom texts for supported langs for now * fix tests * cleanup * cleanup * cleanup * lint * unsupported preferred lang is allowed * fix integration test * finish reverting to old property name * finish reverting to old property name * load languages * refactor(i18n): centralize translators and fs * lint * amplify no validations on preferred languages * fix integration test * lint * fix resetting allowed languages * test unchanged restrictions
608 lines
17 KiB
Go
608 lines
17 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/muhlemmer/gu"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
)
|
|
|
|
func TestCommands_RequestPasswordReset(t *testing.T) {
|
|
type fields struct {
|
|
checkPermission domain.PermissionCheck
|
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
|
userEncryption crypto.EncryptionAlgorithm
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "missing userID",
|
|
fields: fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
wantErr: caos_errs.ThrowInvalidArgument(nil, "COMMAND-SAFdda", "Errors.User.IDMissing"),
|
|
},
|
|
{
|
|
name: "user not existing",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowNotFound(nil, "COMMAND-SAF4f", "Errors.User.NotFound"),
|
|
},
|
|
{
|
|
name: "user not initialized",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Sfe4g", "Errors.User.NotInitialised"),
|
|
},
|
|
{
|
|
name: "missing permission",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
checkPermission: tt.fields.checkPermission,
|
|
eventstore: tt.fields.eventstore(t),
|
|
userEncryption: tt.fields.userEncryption,
|
|
}
|
|
_, _, err := c.RequestPasswordReset(tt.args.ctx, tt.args.userID)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
// successful cases are tested in TestCommands_requestPasswordReset
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_RequestPasswordResetReturnCode(t *testing.T) {
|
|
type fields struct {
|
|
checkPermission domain.PermissionCheck
|
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
|
userEncryption crypto.EncryptionAlgorithm
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "missing userID",
|
|
fields: fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
wantErr: caos_errs.ThrowInvalidArgument(nil, "COMMAND-SAFdda", "Errors.User.IDMissing"),
|
|
},
|
|
{
|
|
name: "user not existing",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowNotFound(nil, "COMMAND-SAF4f", "Errors.User.NotFound"),
|
|
},
|
|
{
|
|
name: "user not initialized",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Sfe4g", "Errors.User.NotInitialised"),
|
|
},
|
|
{
|
|
name: "missing permission",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
checkPermission: tt.fields.checkPermission,
|
|
eventstore: tt.fields.eventstore(t),
|
|
userEncryption: tt.fields.userEncryption,
|
|
}
|
|
_, _, err := c.RequestPasswordResetReturnCode(tt.args.ctx, tt.args.userID)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
// successful cases are tested in TestCommands_requestPasswordReset
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_RequestPasswordResetURLTemplate(t *testing.T) {
|
|
type fields struct {
|
|
checkPermission domain.PermissionCheck
|
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
|
userEncryption crypto.EncryptionAlgorithm
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
urlTmpl string
|
|
notificationType domain.NotificationType
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "invalid template",
|
|
fields: fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args: args{
|
|
userID: "user1",
|
|
urlTmpl: "{{",
|
|
},
|
|
wantErr: caos_errs.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"),
|
|
},
|
|
|
|
{
|
|
name: "missing userID",
|
|
fields: fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
wantErr: caos_errs.ThrowInvalidArgument(nil, "COMMAND-SAFdda", "Errors.User.IDMissing"),
|
|
},
|
|
{
|
|
name: "user not existing",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowNotFound(nil, "COMMAND-SAF4f", "Errors.User.NotFound"),
|
|
},
|
|
{
|
|
name: "user not initialized",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Sfe4g", "Errors.User.NotInitialised"),
|
|
},
|
|
{
|
|
name: "missing permission",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
wantErr: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
checkPermission: tt.fields.checkPermission,
|
|
eventstore: tt.fields.eventstore(t),
|
|
userEncryption: tt.fields.userEncryption,
|
|
}
|
|
_, _, err := c.RequestPasswordResetURLTemplate(tt.args.ctx, tt.args.userID, tt.args.urlTmpl, tt.args.notificationType)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
// successful cases are tested in TestCommands_requestPasswordReset
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_requestPasswordReset(t *testing.T) {
|
|
type fields struct {
|
|
checkPermission domain.PermissionCheck
|
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
|
userEncryption crypto.EncryptionAlgorithm
|
|
newCode cryptoCodeFunc
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
returnCode bool
|
|
urlTmpl string
|
|
notificationType domain.NotificationType
|
|
}
|
|
type res struct {
|
|
details *domain.ObjectDetails
|
|
code *string
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
res res
|
|
}{
|
|
{
|
|
name: "missing userID",
|
|
fields: fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
res: res{
|
|
err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-SAFdda", "Errors.User.IDMissing"),
|
|
},
|
|
},
|
|
{
|
|
name: "user not existing",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
res: res{
|
|
err: caos_errs.ThrowNotFound(nil, "COMMAND-SAF4f", "Errors.User.NotFound"),
|
|
},
|
|
},
|
|
{
|
|
name: "user not initialized",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanInitialCodeAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{CryptoType: crypto.TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("code")}, 10*time.Second),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
res: res{
|
|
err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Sfe4g", "Errors.User.NotInitialised"),
|
|
},
|
|
},
|
|
{
|
|
name: "missing permission",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
res: res{
|
|
err: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
|
},
|
|
},
|
|
{
|
|
name: "code generated",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
expectPush(
|
|
user.NewHumanPasswordCodeAddedEventV2(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
10*time.Minute,
|
|
domain.NotificationTypeEmail,
|
|
"",
|
|
false,
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newCode: mockCode("code", 10*time.Minute),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
res: res{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
},
|
|
code: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "code generated template",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
expectPush(
|
|
user.NewHumanPasswordCodeAddedEventV2(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
10*time.Minute,
|
|
domain.NotificationTypeEmail,
|
|
"https://example.com/password/changey?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
|
false,
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newCode: mockCode("code", 10*time.Minute),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
urlTmpl: "https://example.com/password/changey?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
|
},
|
|
res: res{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
},
|
|
code: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "code generated template sms",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
expectPush(
|
|
user.NewHumanPasswordCodeAddedEventV2(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
10*time.Minute,
|
|
domain.NotificationTypeSms,
|
|
"https://example.com/password/changey?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
|
false,
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newCode: mockCode("code", 10*time.Minute),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
urlTmpl: "https://example.com/password/changey?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
|
notificationType: domain.NotificationTypeSms,
|
|
},
|
|
res: res{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
},
|
|
code: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "code generated returned",
|
|
fields: fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
"username", "firstname", "lastname", "nickname", "displayname",
|
|
language.English, domain.GenderUnspecified, "email", false),
|
|
),
|
|
),
|
|
expectPush(
|
|
user.NewHumanPasswordCodeAddedEventV2(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
10*time.Minute,
|
|
domain.NotificationTypeEmail,
|
|
"",
|
|
true,
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newCode: mockCode("code", 10*time.Minute),
|
|
},
|
|
args: args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
returnCode: true,
|
|
},
|
|
res: res{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
},
|
|
code: gu.Ptr("code"),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
checkPermission: tt.fields.checkPermission,
|
|
eventstore: tt.fields.eventstore(t),
|
|
userEncryption: tt.fields.userEncryption,
|
|
newCode: tt.fields.newCode,
|
|
}
|
|
got, gotPlainCode, err := c.requestPasswordReset(tt.args.ctx, tt.args.userID, tt.args.returnCode, tt.args.urlTmpl, tt.args.notificationType)
|
|
require.ErrorIs(t, err, tt.res.err)
|
|
assert.Equal(t, tt.res.details, got)
|
|
assert.Equal(t, tt.res.code, gotPlainCode)
|
|
})
|
|
}
|
|
}
|