fix: fire resolvespec AfterRead/AfterCreate/AfterDelete, key restheadspec total cache by record id

This commit is contained in:
2026-09-30 23:40:32 +02:00
parent 5933637a88
commit a65ca5f5ce
8 changed files with 448 additions and 13 deletions
+2
View File
@@ -21,6 +21,8 @@ Every DB statement and every DB-touching hook of one request runs on one transac
\* resolvespec, websocketspec, mqttspec, resolvemcp. restheadspec runs `AfterRead` in tx 2.
† restheadspec, websocketspec, mqttspec run `AfterUpdate` in tx 2. resolvespec, resolvemcp run it in tx 1.
resolvespec has no tx 2 for create: `AfterCreate` runs in tx 1, once per record (per item in a batch), after the insert and re-fetch. Its `AfterRead` gets the scanned slice (single and list reads) and may mask in place; a failing `AfterRead` fails the read (fail closed).
Tx 2 exists so the re-fetch sees trigger changes from the committed write.
## Per spec
+45
View File
@@ -97,3 +97,48 @@ func TestSpecHandlersDoNotQueryThePoolDirectly(t *testing.T) {
}
}
}
// Hook types that are defined but deliberately or knowingly never executed.
// Anything else defined in a spec's hooks.go must have an Execute call site: an
// unwired hook silently disables whatever is registered on it (resolvespec's
// AfterRead skipped column-level security masking until it was wired).
var unwiredHooks = map[string]string{
"websocketspec/BeforeDisconnect": "connection close is not hooked yet",
"websocketspec/AfterDisconnect": "connection close is not hooked yet",
}
var hookConstRE = regexp.MustCompile(`(?m)^\s*([A-Z][A-Za-z0-9]*)\s+HookType\s*=`)
func TestEveryDefinedHookHasACallSite(t *testing.T) {
for _, spec := range []string{"resolvespec", "restheadspec", "websocketspec", "resolvemcp", "funcspec"} {
raw, err := os.ReadFile(filepath.Join("..", spec, "hooks.go"))
if err != nil {
t.Fatal(err)
}
var src strings.Builder
files, _ := filepath.Glob(filepath.Join("..", spec, "*.go"))
for _, f := range files {
if strings.HasSuffix(f, "_test.go") || strings.HasSuffix(f, "hooks.go") || strings.HasSuffix(f, "hooks_example.go") {
continue
}
b, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
src.Write(b)
}
for _, m := range hookConstRE.FindAllStringSubmatch(string(raw), -1) {
name := m[1]
if name == "BeforeOp" || name == "OnTxBegin" { // fired by the registry / runInTx
continue
}
if _, ok := unwiredHooks[spec+"/"+name]; ok {
continue
}
call := regexp.MustCompile(`Execute(BeforeOp)?\(` + name + `\b`)
if !call.MatchString(src.String()) {
t.Errorf("%s: hook %s is defined but never executed", spec, name)
}
}
}
}