package diary import ( "io" "os" "path/filepath" "sync" "testing" "time" "git.golem15.com/golem15/summercms/modules/bonfire" ) // installDefault builds the default configuration (console and daily) on // root and installs it, as Setup does. func installDefault(t *testing.T, root string, color bonfire.ColorMode) *channels { t.Helper() opts := Options{ Root: root, Stderr: io.Discard, Environ: []string{}, Color: color, Now: func() time.Time { return time.Date(2026, 10, 21, 12, 0, 0, 0, time.Local) }, }.normalize() conf := DefaultConfig() ch, err := buildChannels(conf, opts) if err != nil { t.Fatal(err) } install(conf, ch, opts) return ch } // dailyOf returns the daily writer of built channels. func dailyOf(t *testing.T, ch *channels) *dailyWriter { t.Helper() for _, c := range ch.closers { if d, ok := c.(*dailyWriter); ok { return d } } t.Fatal("no daily writer in the channels") return nil } func (d *dailyWriter) hasFile() bool { d.mu.Lock() defer d.mu.Unlock() return d.file != nil } // TestRetiredDailyWriterKeepsNoFile (WR-06): a daily writer replaced by a // new setup is retired: a late record through it (from a goroutine that // still holds the old state) is appended to the day's file and the file is // closed again. diary.Close keeps letting the installed writers reopen. func TestRetiredDailyWriterKeepsNoFile(t *testing.T) { t.Cleanup(Reset) root := t.TempDir() file := filepath.Join(root, "storage", "logs", "summer-2026-10-21.log") old := dailyOf(t, installDefault(t, root, bonfire.ColorNever)) if _, err := old.Write([]byte("first\n")); err != nil { t.Fatal(err) } installed := dailyOf(t, installDefault(t, root, bonfire.ColorNever)) if old.hasFile() { t.Fatal("the replaced writer still holds its file") } if _, err := old.Write([]byte("late\n")); err != nil { t.Fatal(err) } if old.hasFile() { t.Error("a late record through the replaced writer left its file open") } if _, err := installed.Write([]byte("current\n")); err != nil { t.Fatal(err) } if err := Close(); err != nil { t.Fatal(err) } if _, err := installed.Write([]byte("reopened\n")); err != nil { t.Fatal(err) } if !installed.hasFile() { t.Error("the installed writer did not reopen its file after diary.Close") } raw, err := os.ReadFile(file) if err != nil { t.Fatal(err) } if got := string(raw); got != "first\nlate\ncurrent\nreopened\n" { t.Errorf("daily file = %q", got) } } // TestApplyGlobalsRebuildsOnce (WR-06): concurrent ApplyGlobals calls with // a new color mode rebuild the channels once, and every call's verbosity // is applied. func TestApplyGlobalsRebuildsOnce(t *testing.T) { t.Cleanup(Reset) installDefault(t, t.TempDir(), bonfire.ColorAuto) before := gens.Load() var start, done sync.WaitGroup start.Add(1) for range 50 { done.Add(1) go func() { defer done.Done() start.Wait() ApplyGlobals(bonfire.Globals{Color: bonfire.ColorNever, Verbosity: 2}) }() } start.Done() done.Wait() if n := gens.Load() - before; n != 1 { t.Errorf("the channels were rebuilt %d times, want 1", n) } st := current.Load() if st.opts.Color != bonfire.ColorNever || st.verbosity != 2 || Verbosity() != 2 { t.Errorf("state after ApplyGlobals: color %v, verbosity %d (Verbosity %d)", st.opts.Color, st.verbosity, Verbosity()) } }