Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/pkg/auditext/event/member/member.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,22 @@ func parsePreResolved(info string) (string, string) {
return info, ""
}

// newBeegoOrm builds a fresh, non-transaction ORM. It is a package-level seam
// so tests can exercise the transaction-replacement branch of ensureORMContext
// without a registered default database (beegoorm.NewOrm panics otherwise).
var newBeegoOrm = beegoorm.NewOrm

func ensureORMContext(ctx context.Context) context.Context {
if ctx == nil {
return orm.Context()
}
if _, err := orm.FromContext(ctx); err == nil {
return ctx
if o, err := orm.FromContext(ctx); err == nil {
if _, ok := o.(beegoorm.TxOrmer); !ok {
return ctx
}
}
return orm.NewContext(ctx, beegoorm.NewOrm())
// Member audit events are resolved asynchronously and may run after the
// request transaction has already been committed/rolled back. Replace
// transaction-bound ORM with a fresh ORM to avoid using a completed tx.
return orm.NewContext(ctx, newBeegoOrm())
}
42 changes: 42 additions & 0 deletions src/pkg/auditext/event/member/member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ import (
"net/http"
"testing"

beegoorm "github.com/beego/beego/v2/client/orm"

"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
"github.com/goharbor/harbor/src/controller/event/model"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/notifier/event"
ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
)

func stubLookups() func() {
Expand Down Expand Up @@ -390,6 +394,44 @@ func TestAuditLogMemberEventEnabled_EmptyOperation(t *testing.T) {
}
}

func TestEnsureORMContext(t *testing.T) {
// Stub the ORM factory so the transaction-replacement branch does not need a
// registered default DB (beegoorm.NewOrm panics otherwise).
origNewOrm := newBeegoOrm
defer func() { newBeegoOrm = origNewOrm }()
fresh := &ormtesting.FakeOrmer{}
newBeegoOrm = func() beegoorm.Ormer { return fresh }

t.Run("non-tx ORM returned as-is", func(t *testing.T) {
// A regular (non-tx) ORM must be returned unchanged, covering the
// TxOrmer type-assertion branch in ensureORMContext.
ctx := orm.NewContext(context.Background(), &ormtesting.FakeOrmer{})
if got := ensureORMContext(ctx); got != ctx {
t.Error("ensureORMContext with non-tx ORM should return the same context")
}
})

t.Run("tx ORM replaced with fresh non-tx ORM", func(t *testing.T) {
// A transaction ORM must be swapped for a fresh non-tx ORM, guarding the
// regression where member audit events reused a completed transaction.
ctx := orm.NewContext(context.Background(), &ormtesting.FakeTxOrmer{})
got := ensureORMContext(ctx)
if got == ctx {
t.Fatal("ensureORMContext with tx ORM should return a new context")
}
o, err := orm.FromContext(got)
if err != nil {
t.Fatalf("orm.FromContext returned error: %v", err)
}
if _, isTx := o.(beegoorm.TxOrmer); isTx {
t.Error("ensureORMContext should replace the transaction ORM with a non-tx ORM")
}
if o != fresh {
t.Error("ensureORMContext should install the ORM produced by newBeegoOrm")
}
})
}

func TestParsePreResolved(t *testing.T) {
tests := []struct {
input string
Expand Down
Loading