mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-10 05:01:28 -10:00
Surfaced by a full-tree audit; each ships a regression test where the behavior is deterministically testable. - swarm: deliver a handoff's mailbox note BEFORE registering the new task, so a mailbox failure can't leave a phantom pending task in the coordinator. - specialist: put a foreground sub-agent child in its own process group and group-kill on cancel/timeout (+ WaitDelay backstop), so a build/server it forked dies with it instead of orphaning. - oauth: serialize on-demand token refresh per key (keyed mutex) and re-load inside the lock, so parallel callers don't each spend the single-use refresh token (all but one would otherwise fail at the token endpoint). - mcp: treat a "source already gone" rename during legacy-token migration as success, so a concurrent migrator doesn't wrongly drop an OAuth MCP server. - cron: claim a due job atomically (advance NextRunAt under the per-job lock) before running it, so two schedulers can't double-fire the same job.
42 lines
1.1 KiB
Go
42 lines
1.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Gitlawb/zero/internal/cron"
|
|
)
|
|
|
|
func TestFireJobClaimPreventsDoubleFire(t *testing.T) {
|
|
now := time.Date(2026, 6, 9, 9, 0, 0, 0, time.UTC)
|
|
nowFn := func() time.Time { return now }
|
|
store := cron.NewStore(cron.StoreOptions{RootDir: t.TempDir(), Now: nowFn})
|
|
due, _ := store.Add(cron.Job{Expr: "0 9 * * *", Prompt: "fire me", Status: cron.StatusActive, NextRunAt: now.Add(-time.Minute)})
|
|
|
|
fx := &fakeExec{}
|
|
// Two schedulers fire the same due job concurrently; the atomic claim must let
|
|
// exactly one through, so the job never double-fires (M9).
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 2; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
job, _ := store.Get(due.ID)
|
|
var out, errb bytes.Buffer
|
|
fireJob(store, nowFn, job, &out, &errb, fx.run)
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
if len(fx.calls) != 1 {
|
|
t.Fatalf("claim must let exactly one scheduler fire the due job, got %d", len(fx.calls))
|
|
}
|
|
// And NextRunAt is advanced to the next slot (tomorrow 09:00), not left due.
|
|
d, _ := store.Get(due.ID)
|
|
if !d.NextRunAt.After(now) {
|
|
t.Fatalf("NextRunAt must advance past now, got %s", d.NextRunAt)
|
|
}
|
|
}
|