Files
zeekayandHanzo Dev 616e4b58d7 a hook is about a Base, and which Base arrives with the event
An extension's hooks were stated once, on the Base the process started with,
so a write to a tenant's Base fired nothing an extension had asked for. The
file says what to do when a record is created; it does not say where, and the
where comes with the event.

So a declaration is recorded as something that can be stated on any Base, and
stated on each as it is constructed — the same place realtime is bound, for
the same reason. A hook still sees only the Base it fired on.

This grants an extension nothing it did not have. A file on disk is the
operator's, and it already ran with the process's authority; what changes is
which data its hooks see. An extension written when there was one Base now
runs against each, which is a behaviour change and is the thing to know.

Routes and crons an extension declares stay on the Base that loaded it: a
route is registered once on a router, and a schedule belongs to the process.

A tenant's Base was bootstrapped and never served, so nothing it scheduled
ever ran — its logs were never cleaned and its database never optimised.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 19:56:46 -07:00

63 lines
2.1 KiB
Go

package org
import (
"testing"
"github.com/hanzoai/base/tests"
)
// A Base looks after itself. The periodic checkpoint and the log cleanup are
// registered where a Base is constructed and where its logger is built, and a
// schedule starts ticking when it is added — so an org's Base keeps its own
// write-ahead log checkpointed and its own logs inside its own retention,
// without anything serving on it.
//
// The alternative reading is easy to arrive at and wrong: __hzCronStart__ is
// bound on OnServe, and a tenant's Base is bootstrapped and never served, so it
// looks as though its schedules never start. Start resumes what Stop paused;
// nothing here is ever paused. The two schedules below are what would silently
// stop if that ever became true, and neither failure announces itself — a Base
// that is not checkpointed grows a write-ahead log, and one whose logs are not
// cleaned keeps them past the days its settings allow.
//
// The autobackup schedule is the third of the set and is absent until a Base's
// settings name a cron for it, which is the same on every Base.
func TestATenantsBaseMaintainsItself(t *testing.T) {
app, err := tests.NewTestApp()
if err != nil {
t.Fatal(err)
}
defer app.Cleanup()
b := newBases(&plugin{app: app, orgDB: NewOrgDB(app, "")})
tenant, err := b.base("acme")
if err != nil {
t.Fatal(err)
}
for _, id := range []string{"__hzDBOptimize__", "__hzLogsCleanup__"} {
if !tenant.Cron().HasJob(id) {
t.Fatalf("an org's Base does not run %s, so nothing maintains it", id)
}
}
if tenant.Cron().HasJob("__hzAutoBackup__") {
t.Fatal("an org's Base scheduled a backup its settings did not ask for")
}
// Registered is not running. This is the half that the OnServe reading
// would take away.
if !tenant.Cron().HasStarted() {
t.Fatal("an org's Base registered its maintenance and never started it")
}
// And they belong to the Base: closing it stops them, so a process that
// opens many orgs is not left ticking for the ones it has released.
b.close()
if tenant.Cron().HasStarted() {
t.Fatal("an org's Base kept ticking after it was closed")
}
}