01fd572a89
## Summary Completed Phase 5 refactoring and reached the target architecture. **Architectural milestone achieved:** - Service layer owns all state and is the sole writer - UI is a thin Fyne view, all widget updates marshaled via `fyne.Do` - Core engines are stateless and injectable - Domain types are pure (no `yaml:"-"` fields) - Full module builds and `go vet ./...` clean ## Changes - Bump version: 0.3.6 → 0.4.0 - Update CHANGELOG with Phase 5 summary - Add ROADMAP "Refactoring Follow-Ups" section ## Known follow-up work 1. **Linux test build broken** — `runner_test.go` needs `//go:build windows` tag 2. **File-size limits exceeded** — `operations.go` (486 lines), `jobs_view.go` (415 lines) See ROADMAP.md for details. --------- Co-authored-by: mixeme <mix.public@ya.ru> Reviewed-on: #1
104 lines
2.9 KiB
Go
104 lines
2.9 KiB
Go
package domain
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseRejectsInvalidSchedules(t *testing.T) {
|
|
cases := []struct {
|
|
spec string
|
|
desc string
|
|
}{
|
|
{"", "empty string"},
|
|
{" ", "whitespace only"},
|
|
{"@every", "bare @every without duration"},
|
|
{"@every ", "@every with trailing space but no duration"},
|
|
{"@every xyz", "invalid @every duration string"},
|
|
{"@every -1s", "negative @every duration"},
|
|
{"@every 0s", "zero @every duration"},
|
|
{"not-a-cron", "invalid cron expression"},
|
|
{"60 * * * *", "cron minute out of range"},
|
|
{"* * * *", "too few cron fields"},
|
|
}
|
|
for _, tc := range cases {
|
|
if _, err := Parse(tc.spec); err == nil {
|
|
t.Errorf("Parse(%q) [%s]: expected error, got nil", tc.spec, tc.desc)
|
|
}
|
|
if err := Validate(tc.spec); err == nil {
|
|
t.Errorf("Validate(%q) [%s]: expected error, got nil", tc.spec, tc.desc)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseEveryInterval(t *testing.T) {
|
|
from := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC)
|
|
s, err := Parse("@every 10s")
|
|
if err != nil {
|
|
t.Fatalf("Parse(@every 10s): unexpected error: %v", err)
|
|
}
|
|
if got, want := s.Next(from), from.Add(10*time.Second); !got.Equal(want) {
|
|
t.Fatalf("Next: got %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseEveryTrimsSurroundingWhitespace(t *testing.T) {
|
|
from := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC)
|
|
s, err := Parse(" @every 90m ")
|
|
if err != nil {
|
|
t.Fatalf("Parse: unexpected error: %v", err)
|
|
}
|
|
if got, want := s.Next(from), from.Add(90*time.Minute); !got.Equal(want) {
|
|
t.Fatalf("Next: got %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseCronExpression(t *testing.T) {
|
|
from := time.Date(2026, 6, 14, 12, 3, 0, 0, time.UTC)
|
|
s, err := Parse("*/5 * * * *")
|
|
if err != nil {
|
|
t.Fatalf("Parse(*/5 * * * *): unexpected error: %v", err)
|
|
}
|
|
want := time.Date(2026, 6, 14, 12, 5, 0, 0, time.UTC)
|
|
if got := s.Next(from); !got.Equal(want) {
|
|
t.Fatalf("Next: got %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseCronDescriptor(t *testing.T) {
|
|
from := time.Date(2026, 6, 14, 12, 3, 0, 0, time.UTC)
|
|
s, err := Parse("@daily")
|
|
if err != nil {
|
|
t.Fatalf("Parse(@daily): unexpected error: %v", err)
|
|
}
|
|
want := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
|
|
if got := s.Next(from); !got.Equal(want) {
|
|
t.Fatalf("Next: got %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestValidateAcceptsValidSchedules(t *testing.T) {
|
|
for _, spec := range []string{"@every 1s", "*/5 * * * *", "0 9 * * 1", "@hourly"} {
|
|
if err := Validate(spec); err != nil {
|
|
t.Errorf("Validate(%q): unexpected error: %v", spec, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestZeroScheduleNextIsZero(t *testing.T) {
|
|
var s Schedule
|
|
if got := s.Next(time.Now()); !got.IsZero() {
|
|
t.Fatalf("zero Schedule Next: got %s, want zero time", got)
|
|
}
|
|
}
|
|
|
|
func TestStringReturnsTrimmedSpec(t *testing.T) {
|
|
s, err := Parse(" */5 * * * * ")
|
|
if err != nil {
|
|
t.Fatalf("Parse: unexpected error: %v", err)
|
|
}
|
|
if got, want := s.String(), "*/5 * * * *"; got != want {
|
|
t.Fatalf("String: got %q, want %q", got, want)
|
|
}
|
|
}
|