/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tm import ( "context" "reflect" "testing" "time" "github.com/agiledragon/gomonkey/v2" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/seata/seata-go/pkg/protocol/message" ) func TestTransactionExecutorBegin(t *testing.T) { type Test struct { ctx context.Context gc *GtxConfig xid string wantHasMock bool wantMockTargetName string wantMockFunction interface{} wantHasError bool wantErrorString string wantUseExist bool wantBeginNew bool } gts := []Test{ { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: NotSupported, }, xid: "123456", }, { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Supports, }, xid: "123456", wantUseExist: true, }, { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: RequiresNew, }, xid: "123456", wantHasMock: true, wantMockTargetName: "Begin", wantMockFunction: func(_ *GlobalTransactionManager, ctx context.Context, i time.Duration) error { SetXID(ctx, "123456") return nil }, wantBeginNew: true, }, // use exist { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Required, }, xid: "123456", wantUseExist: true, }, //Begin new { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Required, }, wantHasMock: true, wantMockTargetName: "Begin", wantMockFunction: func(_ *GlobalTransactionManager, ctx context.Context, i time.Duration) error { SetXID(ctx, "123456") return nil }, wantBeginNew: true, }, // has error { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Never, }, xid: "123456", wantHasError: true, wantErrorString: "existing transaction found for transaction marked with pg 'never', xid = 123456", }, // has not error { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Never, }, }, // use exist { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Mandatory, }, xid: "123456", wantUseExist: true, }, // has error { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: Mandatory, }, wantHasError: true, wantErrorString: "no existing transaction found for transaction marked with pg 'mandatory'", }, // default case { ctx: context.Background(), gc: &GtxConfig{ Name: "MockGtxName", Propagation: -1, }, wantHasError: true, wantErrorString: "not Supported Propagation:-1", }, } for i, v := range gts { t.Logf("Case %v: %+v", i, v) var stub *gomonkey.Patches // set up stub if v.wantHasMock { stub = gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), v.wantMockTargetName, v.wantMockFunction) } v.ctx = InitSeataContext(v.ctx) if v.xid != "" { SetXID(v.ctx, v.xid) } err := begin(v.ctx, v.gc) if v.wantHasError { assert.NotNil(t, err) } else { assert.Nil(t, err) } if v.wantBeginNew { assert.Equal(t, Launcher, *GetTxRole(v.ctx)) } if v.wantUseExist { assert.Equal(t, Participant, *GetTxRole(v.ctx)) } // rest up stub if v.wantHasMock { stub.Reset() } } } func TestTransactionExecutorCommit(t *testing.T) { ctx := context.Background() ctx = InitSeataContext(ctx) SetTxRole(ctx, Launcher) SetTxStatus(ctx, message.GlobalStatusBegin) SetXID(ctx, "") assert.Equal(t, "Commit xid should not be empty", commitOrRollback(ctx, true).Error()) } func TestTransactionExecurotRollback(t *testing.T) { ctx := context.Background() ctx = InitSeataContext(ctx) SetTxRole(ctx, Launcher) SetTxStatus(ctx, message.GlobalStatusBegin) SetXID(ctx, "") errActual := commitOrRollback(ctx, false) assert.Equal(t, "Rollback xid should not be empty", errActual.Error()) } func TestCommitOrRollback(t *testing.T) { type Test struct { ctx context.Context tx GlobalTransaction ok bool wantHasMock bool wantMockTargetName string wantMockFunction interface{} wantHasError bool wantErrorString string } gts := []Test{ { ctx: context.Background(), tx: GlobalTransaction{ TxRole: UnKnow, }, wantHasError: true, wantErrorString: "global transaction role is UnKnow.", }, //ok with nil { ctx: context.Background(), tx: GlobalTransaction{ TxRole: Launcher, }, ok: true, wantHasMock: true, wantMockTargetName: "Commit", wantMockFunction: func(_ *GlobalTransactionManager, ctx context.Context, gtr *GlobalTransaction) error { return nil }, }, //ok with error { ctx: context.Background(), tx: GlobalTransaction{ TxRole: Launcher, }, ok: true, wantHasMock: true, wantMockTargetName: "Commit", wantMockFunction: func(_ *GlobalTransactionManager, ctx context.Context, gtr *GlobalTransaction) error { return errors.New("Mock error") }, wantHasError: true, wantErrorString: "Mock error", }, // false with nil { ctx: context.Background(), tx: GlobalTransaction{ TxRole: Launcher, }, ok: false, wantHasMock: true, wantMockTargetName: "Rollback", wantMockFunction: func(_ *GlobalTransactionManager, ctx context.Context, gtr *GlobalTransaction) error { return nil }, }, // false with error { ctx: context.Background(), tx: GlobalTransaction{ TxRole: Launcher, }, ok: false, wantHasMock: true, wantMockTargetName: "Rollback", wantMockFunction: func(_ *GlobalTransactionManager, ctx context.Context, gtr *GlobalTransaction) error { return errors.New("Mock error") }, wantHasError: true, wantErrorString: "Mock error", }, { ctx: context.Background(), tx: GlobalTransaction{ TxRole: Participant, }, }, } for i, v := range gts { t.Logf("Case %v: %+v", i, v) v.ctx = InitSeataContext(v.ctx) SetTx(v.ctx, &v.tx) var stub *gomonkey.Patches if v.wantHasMock { stub = gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), v.wantMockTargetName, v.wantMockFunction) } err := commitOrRollback(v.ctx, v.ok) if v.wantHasError { assert.Equal(t, v.wantErrorString, err.Error()) } else { assert.Nil(t, err) } if v.wantHasMock { stub.Reset() } } } func TestClearTxConf(t *testing.T) { ctx := InitSeataContext(context.Background()) SetTx(ctx, &GlobalTransaction{ Xid: "123456", TxName: "MockTxName", TxStatus: message.GlobalStatusBegin, TxRole: Launcher, }) clearTxConf(ctx) assert.Equal(t, "123456", GetXID(ctx)) assert.Equal(t, UnKnow, *GetTxRole(ctx)) assert.Equal(t, message.GlobalStatusUnKnown, *GetTxStatus(ctx)) assert.Equal(t, "", GetTxName(ctx)) } func TestUseExistGtx(t *testing.T) { ctx := InitSeataContext(context.Background()) SetXID(ctx, "123456") useExistGtx(ctx, &GtxConfig{ Name: "useExistGtxMock", }) assert.Equal(t, Participant, *GetTxRole(ctx)) assert.Equal(t, message.GlobalStatusBegin, *GetTxStatus(ctx)) } func TestBeginNewGtx(t *testing.T) { ctx := InitSeataContext(context.Background()) g := &GtxConfig{ Name: "beginNewGtxMock", } // case return nil gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), "Begin", func(_ *GlobalTransactionManager, ctx context.Context, timeout time.Duration) error { return nil }) assert.Nil(t, beginNewGtx(ctx, g)) assert.Equal(t, Launcher, *GetTxRole(ctx)) assert.Equal(t, g.Name, GetTxName(ctx)) assert.Equal(t, message.GlobalStatusBegin, *GetTxStatus(ctx)) // case return error err := errors.New("Mock Exception") gomonkey.ApplyMethod(reflect.TypeOf(GetGlobalTransactionManager()), "Begin", func(_ *GlobalTransactionManager, ctx context.Context, timeout time.Duration) error { return err }) assert.Error(t, beginNewGtx(ctx, g)) } func TestWithGlobalTx(t *testing.T) { callbackError := func(ctx context.Context) error { return errors.New("mock callback error") } callbackNil := func(ctx context.Context) error { return nil } type testCase struct { GtxConfig *GtxConfig occurError bool errMessage string callbackErr bool mockBeginFunc interface{} mockBeginTarget interface{} mockSecondPhaseFunc interface{} mockSecondPhaseTarget interface{} secondErr bool callback CallbackWithCtx } gts := []testCase{ // case TxName is nil { GtxConfig: &GtxConfig{}, occurError: true, errMessage: "global transaction name is required.", }, // case GtxConfig is nil { occurError: true, errMessage: "global transaction config info is required.", }, // case mock begin return error { GtxConfig: &GtxConfig{ Name: "MockGtxConfig", }, occurError: true, errMessage: "mock begin", mockBeginTarget: begin, mockBeginFunc: func(ctx context.Context, gc *GtxConfig) error { return errors.New("mock begin") }, }, // case callback return error { GtxConfig: &GtxConfig{ Name: "MockGtxConfig", }, callbackErr: true, callback: callbackError, mockBeginTarget: begin, mockBeginFunc: func(ctx context.Context, gc *GtxConfig) error { return nil }, }, // case callback return nil { GtxConfig: &GtxConfig{ Name: "MockGtxConfig", }, mockBeginTarget: begin, mockBeginFunc: func(ctx context.Context, gc *GtxConfig) error { SetXID(ctx, "123456") return nil }, callback: callbackNil, mockSecondPhaseTarget: commitOrRollback, mockSecondPhaseFunc: func(ctx context.Context, s bool) error { return nil }, }, // case second mock error { GtxConfig: &GtxConfig{ Name: "MockGtxConfig", }, mockBeginTarget: begin, mockBeginFunc: func(ctx context.Context, gc *GtxConfig) error { SetXID(ctx, "123456") return nil }, callback: callbackNil, mockSecondPhaseTarget: commitOrRollback, mockSecondPhaseFunc: func(ctx context.Context, s bool) error { return errors.New("second error mock") }, secondErr: true, }, } for i, v := range gts { t.Logf("Case %v: %+v", i, v) var beginStub *gomonkey.Patches var secondStub *gomonkey.Patches if v.mockBeginTarget != nil { beginStub = gomonkey.ApplyFunc(v.mockBeginTarget, v.mockBeginFunc) } if v.mockSecondPhaseTarget != nil { secondStub = gomonkey.ApplyFunc(v.mockSecondPhaseTarget, v.mockSecondPhaseFunc) } ctx := context.Background() err := WithGlobalTx(ctx, v.GtxConfig, v.callback) if v.occurError { assert.Equal(t, v.errMessage, err.Error()) } if v.callbackErr { assert.NotNil(t, err) } if v.secondErr { assert.NotNil(t, err) } if v.mockBeginTarget != nil { beginStub.Reset() } if v.mockSecondPhaseTarget != nil { secondStub.Reset() } } }