2020-05-11 08:16:27 +00:00
|
|
|
package eventsourcing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-08 11:56:37 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/caos/zitadel/internal/api/authz"
|
2020-05-11 08:16:27 +00:00
|
|
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
|
|
|
"github.com/caos/zitadel/internal/eventstore/models"
|
|
|
|
"github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestUserGrantAddedAggregate(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
grant *model.UserGrant
|
|
|
|
aggCreator *models.AggregateCreator
|
|
|
|
}
|
|
|
|
type res struct {
|
|
|
|
eventLen int
|
|
|
|
eventType models.EventType
|
|
|
|
errFunc func(err error) bool
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
res res
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "usergrant added ok",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
grant: &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID"},
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventType: model.UserGrantAdded,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
grant: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2020-05-28 04:49:22 +00:00
|
|
|
aggregates, err := UserGrantAddedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.grant)
|
2020-05-11 08:16:27 +00:00
|
|
|
|
2020-05-28 04:49:22 +00:00
|
|
|
if tt.res.errFunc == nil && len(aggregates[0].Events) != tt.res.eventLen {
|
|
|
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[0].Events))
|
2020-05-11 08:16:27 +00:00
|
|
|
}
|
2020-05-28 04:49:22 +00:00
|
|
|
if tt.res.errFunc == nil && aggregates[0].Events[0].Type != tt.res.eventType {
|
|
|
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, aggregates[0].Events[0].Type.String())
|
2020-05-11 08:16:27 +00:00
|
|
|
}
|
2020-05-28 04:49:22 +00:00
|
|
|
if tt.res.errFunc == nil && aggregates[0].Events[0].Data == nil {
|
2020-05-11 08:16:27 +00:00
|
|
|
t.Errorf("should have data in event")
|
|
|
|
}
|
|
|
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
|
|
|
t.Errorf("got wrong err: %v ", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUserGrantChangedAggregate(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
existing *model.UserGrant
|
|
|
|
new *model.UserGrant
|
2020-06-19 13:32:03 +00:00
|
|
|
cascade bool
|
2020-05-11 08:16:27 +00:00
|
|
|
aggCreator *models.AggregateCreator
|
|
|
|
}
|
|
|
|
type res struct {
|
|
|
|
eventLen int
|
|
|
|
eventTypes []models.EventType
|
|
|
|
errFunc func(err error) bool
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
res res
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "change project grant",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"KeyChanged"},
|
|
|
|
},
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventTypes: []models.EventType{model.UserGrantChanged},
|
|
|
|
},
|
|
|
|
},
|
2020-06-19 13:32:03 +00:00
|
|
|
{
|
|
|
|
name: "change project grant cascade",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-06-19 13:32:03 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"KeyChanged"},
|
|
|
|
},
|
|
|
|
cascade: true,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventTypes: []models.EventType{model.UserGrantCascadeChanged},
|
|
|
|
},
|
|
|
|
},
|
2020-05-11 08:16:27 +00:00
|
|
|
{
|
|
|
|
name: "existing grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: nil,
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
new: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2020-06-19 13:32:03 +00:00
|
|
|
agg, err := UserGrantChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new, tt.args.cascade)(tt.args.ctx)
|
2020-05-11 08:16:27 +00:00
|
|
|
|
|
|
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
|
|
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
|
|
|
}
|
|
|
|
for i := 0; i < tt.res.eventLen; i++ {
|
|
|
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
|
|
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
|
|
|
}
|
|
|
|
if tt.res.errFunc == nil && agg.Events[i].Data == nil {
|
|
|
|
t.Errorf("should have data in event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
|
|
|
t.Errorf("got wrong err: %v ", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUserGrantRemovedAggregate(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
existing *model.UserGrant
|
|
|
|
new *model.UserGrant
|
2020-06-19 13:32:03 +00:00
|
|
|
cascade bool
|
2020-05-11 08:16:27 +00:00
|
|
|
aggCreator *models.AggregateCreator
|
|
|
|
}
|
|
|
|
type res struct {
|
|
|
|
eventLen int
|
|
|
|
eventTypes []models.EventType
|
|
|
|
errFunc func(err error) bool
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
res res
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "remove app",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
},
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventTypes: []models.EventType{model.UserGrantRemoved},
|
|
|
|
},
|
|
|
|
},
|
2020-06-19 13:32:03 +00:00
|
|
|
{
|
|
|
|
name: "remove app cascade",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-06-19 13:32:03 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
},
|
|
|
|
cascade: true,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventTypes: []models.EventType{model.UserGrantCascadeRemoved},
|
|
|
|
},
|
|
|
|
},
|
2020-05-11 08:16:27 +00:00
|
|
|
{
|
|
|
|
name: "existing project nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
UserID: "UserID",
|
|
|
|
ProjectID: "ProjectID",
|
|
|
|
RoleKeys: []string{"Key"}},
|
|
|
|
new: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2020-06-19 13:32:03 +00:00
|
|
|
aggregates, err := UserGrantRemovedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new, tt.args.cascade)
|
2020-05-11 08:16:27 +00:00
|
|
|
|
2020-05-28 04:49:22 +00:00
|
|
|
if tt.res.errFunc == nil && len(aggregates[0].Events) != tt.res.eventLen {
|
|
|
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[0].Events))
|
2020-05-11 08:16:27 +00:00
|
|
|
}
|
|
|
|
for i := 0; i < tt.res.eventLen; i++ {
|
2020-05-28 04:49:22 +00:00
|
|
|
if tt.res.errFunc == nil && aggregates[0].Events[i].Type != tt.res.eventTypes[i] {
|
|
|
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], aggregates[0].Events[i].Type.String())
|
2020-05-11 08:16:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
|
|
|
t.Errorf("got wrong err: %v ", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUserGrantDeactivatedAggregate(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
existing *model.UserGrant
|
|
|
|
new *model.UserGrant
|
|
|
|
aggCreator *models.AggregateCreator
|
|
|
|
}
|
|
|
|
type res struct {
|
|
|
|
eventLen int
|
|
|
|
eventTypes []models.EventType
|
|
|
|
errFunc func(err error) bool
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
res res
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "deactivate project grant",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
},
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
},
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventTypes: []models.EventType{model.UserGrantDeactivated},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "existing grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
|
|
|
new: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
agg, err := UserGrantDeactivatedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
|
|
|
|
|
|
|
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
|
|
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
|
|
|
}
|
|
|
|
for i := 0; i < tt.res.eventLen; i++ {
|
|
|
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
|
|
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
|
|
|
t.Errorf("got wrong err: %v ", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUserGrantReactivatedAggregate(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
existing *model.UserGrant
|
|
|
|
new *model.UserGrant
|
|
|
|
aggCreator *models.AggregateCreator
|
|
|
|
}
|
|
|
|
type res struct {
|
|
|
|
eventLen int
|
|
|
|
eventTypes []models.EventType
|
|
|
|
errFunc func(err error) bool
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
res res
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "reactivate project grant",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
},
|
|
|
|
new: &model.UserGrant{
|
|
|
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
|
|
|
},
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
eventLen: 1,
|
|
|
|
eventTypes: []models.EventType{model.UserGrantReactivated},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "existing grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "grant nil",
|
|
|
|
args: args{
|
2020-07-08 11:56:37 +00:00
|
|
|
ctx: authz.NewMockContext("orgID", "userID"),
|
2020-05-11 08:16:27 +00:00
|
|
|
existing: &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
|
|
|
new: nil,
|
|
|
|
aggCreator: models.NewAggregateCreator("Test"),
|
|
|
|
},
|
|
|
|
res: res{
|
|
|
|
errFunc: caos_errs.IsPreconditionFailed,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
agg, err := UserGrantReactivatedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
|
|
|
|
|
|
|
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
|
|
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
|
|
|
}
|
|
|
|
for i := 0; i < tt.res.eventLen; i++ {
|
|
|
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
|
|
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
|
|
|
t.Errorf("got wrong err: %v ", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|