test(12.2-05): cover every Go behaviour of phase 12.2
- lagoon: Date and TimeOfDay through JSON, text and real DATE/TIME columns; Fill text fallback without changing earlier conversions; required on zero dates; deferred_bindings shape, store isolation and envelope; PurgeDeferred cut-off, after-commit blobs, SKIP LOCKED, skipped types and the deferred:purge command - attach: Store limits, extensions, MIME patterns, default lists, key shape and blob cleanup; IsAllowedImage formats, polyglots, ceiling - conga and pact: framework purge schedule entry and forged jobs; the six relation child hook interfaces - cabana: fileupload and datepicker compile, upload, remove, caption, reorder and bounds; deferred commit order, rollback, applied-only and concurrent saves; relation contracts, forms, CRUD, deferral, schema
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/bonfire"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/party"
|
||||
"gorm.io/gorm"
|
||||
@@ -843,3 +844,171 @@ func TestFrameworkScheduleSmoke(t *testing.T) {
|
||||
t.Fatalf("malformed purge_at = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// frameworkApp is an app with config and extra keys set.
|
||||
func frameworkApp(t *testing.T, extra map[string]any) *backpack.App {
|
||||
t.Helper()
|
||||
cfg, err := compass.Open(compass.Options{Dir: t.TempDir(), Env: "testing", Environ: []string{}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for k, v := range extra {
|
||||
if err := cfg.Set(k, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return backpack.New(cfg)
|
||||
}
|
||||
|
||||
// TestFrameworkSchedulePurgeAt covers lagoon.FrameworkSchedule's reading
|
||||
// of database.deferred_bindings.purge_at (D-05): the default 03:00, every
|
||||
// valid HH:MM, an empty or null value disabling the entry, and malformed
|
||||
// values or types failing with the key named.
|
||||
func TestFrameworkSchedulePurgeAt(t *testing.T) {
|
||||
const key = "database.deferred_bindings.purge_at"
|
||||
for _, tc := range []struct {
|
||||
value any
|
||||
set bool
|
||||
cadence pact.Cadence
|
||||
none bool
|
||||
err bool
|
||||
}{
|
||||
{set: false, cadence: pact.DailyAt(3, 0)},
|
||||
{value: "00:00", set: true, cadence: pact.DailyAt(0, 0)},
|
||||
{value: "23:59", set: true, cadence: pact.DailyAt(23, 59)},
|
||||
{value: " 07:05 ", set: true, cadence: pact.DailyAt(7, 5)},
|
||||
{value: "", set: true, none: true},
|
||||
{value: " ", set: true, none: true},
|
||||
{value: nil, set: true, none: true},
|
||||
{value: "24:00", set: true, err: true},
|
||||
{value: "7:00", set: true, err: true},
|
||||
{value: "12:60", set: true, err: true},
|
||||
{value: "12:00:00", set: true, err: true},
|
||||
{value: "noon", set: true, err: true},
|
||||
{value: 300, set: true, err: true},
|
||||
} {
|
||||
extra := map[string]any{}
|
||||
if tc.set {
|
||||
extra[key] = tc.value
|
||||
}
|
||||
got, err := lagoon.FrameworkSchedule(frameworkApp(t, extra))
|
||||
switch {
|
||||
case tc.err:
|
||||
if err == nil || !strings.Contains(err.Error(), key) {
|
||||
t.Fatalf("%v: err = %v, want one naming %s", tc.value, err, key)
|
||||
}
|
||||
case tc.none:
|
||||
if err != nil || len(got) != 0 {
|
||||
t.Fatalf("%q: %+v %v, want no entry", tc.value, got, err)
|
||||
}
|
||||
default:
|
||||
if err != nil || len(got) != 1 || got[0].Command != "deferred:purge" || len(got[0].Args) != 0 || got[0].Cadence != tc.cadence {
|
||||
t.Fatalf("%v: %+v %v", tc.value, got, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for name, app := range map[string]*backpack.App{"nil app": nil, "no config": backpack.New(nil)} {
|
||||
if got, err := lagoon.FrameworkSchedule(app); err != nil || got != nil {
|
||||
t.Fatalf("%s: %+v %v", name, got, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFrameworkScheduleEntries: the framework entry is first, listed under
|
||||
// summercms.lagoon with id summercms.lagoon[0]:deferred:purge, joins the
|
||||
// compiled table at the configured time, and leaves plugin entry ids
|
||||
// unchanged.
|
||||
func TestFrameworkScheduleEntries(t *testing.T) {
|
||||
plugins := schedulePlugins(
|
||||
pact.ScheduledCommand{Command: "acme:tick", Cadence: pact.Daily()},
|
||||
pact.ScheduledCommand{Command: "acme:tock", Cadence: pact.Every(time.Hour)},
|
||||
)
|
||||
entries, err := scheduleEntries(frameworkApp(t, map[string]any{"database.deferred_bindings.purge_at": "05:15"}), plugins)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids := make([]string, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
ids = append(ids, e.id)
|
||||
}
|
||||
if strings.Join(ids, ",") != "summercms.lagoon[0]:deferred:purge,acme.test[0]:acme:tick,acme.test[1]:acme:tock" {
|
||||
t.Fatalf("ids = %v", ids)
|
||||
}
|
||||
if entries[0].plugin != lagoon.FrameworkScheduleID || entries[0].cmd.Cadence != pact.DailyAt(5, 15) {
|
||||
t.Fatalf("framework entry = %+v", entries[0])
|
||||
}
|
||||
_, table, err := periodicJobs(frameworkApp(t, nil), plugins)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fw, ok := table["summercms.lagoon[0]:deferred:purge"]; !ok || fw.Command != "deferred:purge" || fw.Cadence != pact.DailyAt(3, 0) {
|
||||
t.Fatalf("compiled table = %+v", table)
|
||||
}
|
||||
if _, _, err := periodicJobs(frameworkApp(t, map[string]any{"database.deferred_bindings.purge_at": "25:00"}), plugins); err == nil {
|
||||
t.Fatal("a malformed purge_at compiled")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFrameworkScheduleForgedArgs: a job naming the framework entry with
|
||||
// other args or another command is skipped like any forged scheduled job;
|
||||
// the exact entry runs deferred:purge with no args.
|
||||
func TestFrameworkScheduleForgedArgs(t *testing.T) {
|
||||
db, dsn := migratedDB(t)
|
||||
app, _ := testApp(t, db, dsn, nil)
|
||||
logs := &captureHandler{}
|
||||
if err := app.Publish(slog.New(logs)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var mu sync.Mutex
|
||||
var ran [][]string
|
||||
record := func(name string) bonfire.Command {
|
||||
return bonfire.Command{Name: name, Flags: []bonfire.Flag{{Name: "days"}}, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
days, _ := in.Flag("days")
|
||||
ran = append(ran, append([]string{name, "days=" + days}, in.Args()...))
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
if err := app.Publish(bonfire.NewCatalog([]bonfire.Command{record("deferred:purge"), record("acme:other")})); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w, err := StartWorker(t.Context(), app, schedulePlugins(pact.ScheduledCommand{Command: "acme:tick", Cadence: pact.Daily()}), WorkerOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = w.Stop(context.Background()) }()
|
||||
m, err := From(app)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const entry = "summercms.lagoon[0]:deferred:purge"
|
||||
const skip = "schedule: job does not match a compiled schedule entry; skipping"
|
||||
for _, forged := range []ScheduledCommandArgs{
|
||||
{Entry: entry, Command: "deferred:purge", Args: []string{"--days", "0"}},
|
||||
{Entry: entry, Command: "acme:other"},
|
||||
{Entry: "summercms.lagoon[1]:deferred:purge", Command: "deferred:purge"},
|
||||
} {
|
||||
if err := m.runScheduled(t.Context(), forged); err != nil {
|
||||
t.Fatalf("%+v: %v", forged, err)
|
||||
}
|
||||
if !logs.find(slog.LevelWarn, skip, "command", forged.Command) {
|
||||
t.Fatalf("%+v: no skip warning", forged)
|
||||
}
|
||||
}
|
||||
mu.Lock()
|
||||
got := fmt.Sprint(ran)
|
||||
mu.Unlock()
|
||||
if got != "[]" {
|
||||
t.Fatalf("forged framework jobs ran %s", got)
|
||||
}
|
||||
if err := m.runScheduled(t.Context(), ScheduledCommandArgs{Entry: entry, Command: "deferred:purge"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mu.Lock()
|
||||
got = fmt.Sprint(ran)
|
||||
mu.Unlock()
|
||||
if got != "[[deferred:purge days=]]" {
|
||||
t.Fatalf("ran = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user