fix(09): WR-19 expose the write transaction to hooks and scopes through TxFromContext
This commit is contained in:
@@ -26,6 +26,9 @@ type hookController struct {
|
||||
fail string
|
||||
queries atomic.Int32
|
||||
allocs atomic.Int32
|
||||
// txHooks counts the Before hooks that found the write's transaction on
|
||||
// their context through TxFromContext.
|
||||
txHooks atomic.Int32
|
||||
}
|
||||
|
||||
func (c *hookController) ID() string { return "acme.demo.records" }
|
||||
@@ -58,6 +61,7 @@ func (c *hookController) FormExtendQuery(ctx context.Context, db *gorm.DB) *gorm
|
||||
}
|
||||
func (c *hookController) FormBeforeCreate(ctx context.Context, model any) error {
|
||||
recordHook(ctx, "form_before_create")
|
||||
c.noteTx(ctx)
|
||||
if c.trackScope {
|
||||
if row, ok := model.(*crudRow); ok {
|
||||
if scope, ok := ctx.Value(scopeKey{}).(uint); ok {
|
||||
@@ -73,6 +77,7 @@ func (c *hookController) FormAfterCreate(ctx context.Context, model any) error {
|
||||
}
|
||||
func (c *hookController) FormBeforeUpdate(ctx context.Context, model any) error {
|
||||
recordHook(ctx, "form_before_update")
|
||||
c.noteTx(ctx)
|
||||
return c.hookErr("form_before_update")
|
||||
}
|
||||
func (c *hookController) FormAfterUpdate(ctx context.Context, model any) error {
|
||||
@@ -81,12 +86,18 @@ func (c *hookController) FormAfterUpdate(ctx context.Context, model any) error {
|
||||
}
|
||||
func (c *hookController) FormBeforeDelete(ctx context.Context, model any) error {
|
||||
recordHook(ctx, "form_before_delete")
|
||||
c.noteTx(ctx)
|
||||
return c.hookErr("form_before_delete")
|
||||
}
|
||||
func (c *hookController) FormAfterDelete(ctx context.Context, model any) error {
|
||||
recordHook(ctx, "form_after_delete")
|
||||
return c.hookErr("form_after_delete")
|
||||
}
|
||||
func (c *hookController) noteTx(ctx context.Context) {
|
||||
if _, ok := TxFromContext(ctx); ok {
|
||||
c.txHooks.Add(1)
|
||||
}
|
||||
}
|
||||
func (c *hookController) hookErr(name string) error {
|
||||
if c != nil && c.fail == name {
|
||||
return errHookBoom
|
||||
@@ -558,3 +569,31 @@ func TestCRUDOperationsFollowDeclarations(t *testing.T) {
|
||||
t.Fatalf("declared bulk delete=%d %s", got.Code, got.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestHooksReceiveTheWriteTransaction pins WR-19: lifecycle hooks can reach the
|
||||
// transaction their write runs in through TxFromContext, so a hook's own reads
|
||||
// share the write's snapshot and connection instead of using the app pool.
|
||||
func TestHooksReceiveTheWriteTransaction(t *testing.T) {
|
||||
_, httpSvc, _, _, hooks := hookFixture(t)
|
||||
ctx := principalCtx(hooks, superUser())
|
||||
created := crudCall(httpSvc, http.MethodPost, "", []byte(`{"name":"Ada"}`), ctx)
|
||||
if created.Code != http.StatusCreated {
|
||||
t.Fatalf("create=%d %s", created.Code, created.Body.String())
|
||||
}
|
||||
id := uintString(decodeData(t, created.Body.Bytes())["id"])
|
||||
if updated := crudCall(httpSvc, http.MethodPut, id, []byte(`{"name":"Bea"}`), ctx); updated.Code != http.StatusOK {
|
||||
t.Fatalf("update=%d %s", updated.Code, updated.Body.String())
|
||||
}
|
||||
if deleted := crudCall(httpSvc, http.MethodDelete, id, nil, ctx); deleted.Code != http.StatusOK {
|
||||
t.Fatalf("delete=%d %s", deleted.Code, deleted.Body.String())
|
||||
}
|
||||
if got := hooks.txHooks.Load(); got != 3 {
|
||||
t.Fatalf("Before hooks that saw the transaction = %d, want 3 (create, update, delete)", got)
|
||||
}
|
||||
if _, ok := TxFromContext(context.Background()); ok {
|
||||
t.Fatal("a bare context reported a transaction")
|
||||
}
|
||||
if _, ok := TxFromContext(nil); ok { //nolint:staticcheck // a nil context must not panic
|
||||
t.Fatal("a nil context reported a transaction")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user