test(09-05): add failing tests for deterministic bulk delete
- Empty selections are 422 and duplicates run once in primary-key order - A completed retry and an all-absent selection delete nothing and skip hooks - Mixed, hook, cancel, and concurrent requests keep the batch atomic
This commit is contained in:
@@ -7,10 +7,10 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -118,10 +118,23 @@ func (r *crudRow) AfterUpdate(tx *gorm.DB) error {
|
||||
|
||||
func (r *crudRow) BeforeDelete(tx *gorm.DB) error {
|
||||
ctx := txContext(tx)
|
||||
if _, ok := ctx.Value(bulkSlowKey{}).(bool); ok {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
recordHook(ctx, "before_delete")
|
||||
appendDeletedID(ctx, r.ID)
|
||||
if fail, _ := ctx.Value(failDeleteKey{}).(bool); fail {
|
||||
return errors.New("secret-hook-boom")
|
||||
}
|
||||
if ids, ok := ctx.Value(failIDsKey{}).(map[uint]bool); ok && ids[r.ID] {
|
||||
return errors.New("secret-hook-boom")
|
||||
}
|
||||
if id, ok := ctx.Value(cancelOnKey{}).(uint); ok && r.ID == id {
|
||||
if cancel, ok := ctx.Value(cancelKey{}).(context.CancelFunc); ok {
|
||||
cancel()
|
||||
}
|
||||
return context.Canceled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -132,6 +145,13 @@ func (r *crudRow) AfterDelete(tx *gorm.DB) error {
|
||||
|
||||
type hookSinkKey struct{}
|
||||
type failDeleteKey struct{}
|
||||
type failIDsKey struct{}
|
||||
type idSinkKey struct{}
|
||||
type bulkSlowKey struct{}
|
||||
type cancelOnKey struct{}
|
||||
type cancelKey struct{}
|
||||
|
||||
var hookMu sync.Mutex
|
||||
|
||||
func txContext(tx *gorm.DB) context.Context {
|
||||
if tx != nil && tx.Statement != nil && tx.Statement.Context != nil {
|
||||
@@ -144,6 +164,8 @@ func recordHook(ctx context.Context, name string) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
hookMu.Lock()
|
||||
defer hookMu.Unlock()
|
||||
sink, _ := ctx.Value(hookSinkKey{}).(*[]string)
|
||||
if sink == nil {
|
||||
return
|
||||
@@ -151,6 +173,19 @@ func recordHook(ctx context.Context, name string) {
|
||||
*sink = append(*sink, name)
|
||||
}
|
||||
|
||||
func appendDeletedID(ctx context.Context, id uint) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
hookMu.Lock()
|
||||
defer hookMu.Unlock()
|
||||
sink, _ := ctx.Value(idSinkKey{}).(*[]uint)
|
||||
if sink == nil {
|
||||
return
|
||||
}
|
||||
*sink = append(*sink, id)
|
||||
}
|
||||
|
||||
type crudNoRules struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
Name string `gorm:"column:name"`
|
||||
|
||||
Reference in New Issue
Block a user