mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-05 04:51:58 +00:00
fix: fire resolvespec AfterRead/AfterCreate/AfterDelete, key restheadspec total cache by record id
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user