mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-21 02:04:26 +08:00
code refactor and add unit tests
This commit is contained in:
+1
-1
@@ -65,7 +65,7 @@ func listAllCronJobs(c *cli.Context) error {
|
|||||||
|
|
||||||
fmt.Printf("[Name] %s\n", cronJob.Name)
|
fmt.Printf("[Name] %s\n", cronJob.Name)
|
||||||
fmt.Printf("[Description] %s\n", cronJob.Description)
|
fmt.Printf("[Description] %s\n", cronJob.Description)
|
||||||
fmt.Printf("[Interval] Every %s\n", cronJob.Interval)
|
fmt.Printf("[Interval] Every %s\n", cronJob.Period.GetInterval())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (c *CronJobSchedulerContainer) registerAllJobs(config *settings.Config) {
|
|||||||
|
|
||||||
func (c *CronJobSchedulerContainer) registerIntervalJob(job *CronJob) {
|
func (c *CronJobSchedulerContainer) registerIntervalJob(job *CronJob) {
|
||||||
gocronJob, err := c.scheduler.NewJob(
|
gocronJob, err := c.scheduler.NewJob(
|
||||||
gocron.DurationJob(job.Interval),
|
job.Period.ToJobDefinition(),
|
||||||
gocron.NewTask(job.doRun),
|
gocron.NewTask(job.doRun),
|
||||||
gocron.WithName(job.Name),
|
gocron.WithName(job.Name),
|
||||||
gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/duplicatechecker"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCronJobSchedulerContainerRegisterIntervalJob(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
container := &CronJobSchedulerContainer{
|
||||||
|
allJobsMap: make(map[string]*CronJob),
|
||||||
|
allGocronJobsMap: make(map[string]gocron.Job),
|
||||||
|
}
|
||||||
|
|
||||||
|
container.scheduler, err = gocron.NewScheduler(
|
||||||
|
gocron.WithLocation(time.Local),
|
||||||
|
gocron.WithLogger(NewGocronLoggerAdapter()),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
actualValue := false
|
||||||
|
job := &CronJob{
|
||||||
|
Name: "TestRegisterIntervalJob",
|
||||||
|
Description: "The test cron job",
|
||||||
|
Period: CronJobIntervalPeriod{
|
||||||
|
Interval: 1 * time.Second,
|
||||||
|
},
|
||||||
|
Run: func() error {
|
||||||
|
actualValue = true
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
container.scheduler.Start()
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(container.GetAllJobs()))
|
||||||
|
assert.Equal(t, job, container.GetAllJobs()[0])
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
assert.True(t, actualValue)
|
||||||
|
|
||||||
|
err = container.scheduler.Shutdown()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCronJobSchedulerContainerSyncRunJobNow(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
container := &CronJobSchedulerContainer{
|
||||||
|
allJobsMap: make(map[string]*CronJob),
|
||||||
|
allGocronJobsMap: make(map[string]gocron.Job),
|
||||||
|
}
|
||||||
|
|
||||||
|
container.scheduler, err = gocron.NewScheduler(
|
||||||
|
gocron.WithLocation(time.Local),
|
||||||
|
gocron.WithLogger(NewGocronLoggerAdapter()),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
actualValue := false
|
||||||
|
job := &CronJob{
|
||||||
|
Name: "TestSyncRunJob",
|
||||||
|
Description: "The test cron job",
|
||||||
|
Period: CronJobIntervalPeriod{
|
||||||
|
Interval: 24 * time.Hour,
|
||||||
|
},
|
||||||
|
Run: func() error {
|
||||||
|
actualValue = true
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
|
||||||
|
err = container.SyncRunJobNow("TestSyncRunJob")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, actualValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCronJobSchedulerContainerRepeatRun(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
checker, _ := duplicatechecker.NewInMemoryDuplicateChecker(&settings.Config{
|
||||||
|
DuplicateSubmissionsIntervalDuration: 60 * time.Second,
|
||||||
|
InMemoryDuplicateCheckerCleanupIntervalDuration: 60 * time.Second,
|
||||||
|
})
|
||||||
|
|
||||||
|
duplicatechecker.Container.Current = checker
|
||||||
|
|
||||||
|
container := &CronJobSchedulerContainer{
|
||||||
|
allJobsMap: make(map[string]*CronJob),
|
||||||
|
allGocronJobsMap: make(map[string]gocron.Job),
|
||||||
|
}
|
||||||
|
|
||||||
|
container.scheduler, err = gocron.NewScheduler(
|
||||||
|
gocron.WithLocation(time.Local),
|
||||||
|
gocron.WithLogger(NewGocronLoggerAdapter()),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
var runCount atomic.Uint32
|
||||||
|
runTime := time.Now().Add(time.Second)
|
||||||
|
job := &CronJob{
|
||||||
|
Name: "TestRepeatRunJob",
|
||||||
|
Description: "The test cron job",
|
||||||
|
Period: CronJobFixedTimePeriod{
|
||||||
|
Time: runTime,
|
||||||
|
},
|
||||||
|
Run: func() error {
|
||||||
|
runCount.Add(1)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
container.registerIntervalJob(job)
|
||||||
|
container.scheduler.Start()
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, uint32(1), runCount.Load())
|
||||||
|
|
||||||
|
err = container.scheduler.Shutdown()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
+18
-16
@@ -9,33 +9,35 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CronJob represents the cron job instance
|
||||||
type CronJob struct {
|
type CronJob struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Interval time.Duration
|
Period CronJobPeriod
|
||||||
Run func() error
|
Run func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronJob) doRun() {
|
func (c *CronJob) doRun() {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
localAddr, err := utils.GetLocalIPAddressesString()
|
|
||||||
|
|
||||||
if err != nil {
|
if duplicatechecker.Container.Current != nil {
|
||||||
log.Warnf("[cron_job.doRun] job \"%s\" cannot get local ipv4 address, because %s", c.Name, err.Error())
|
localAddr, err := utils.GetLocalIPAddressesString()
|
||||||
return
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[cron_job.doRun] job \"%s\" cannot get local ipv4 address, because %s", c.Name, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentInfo := fmt.Sprintf("ip: %s, startTime: %d", localAddr, time.Now().Unix())
|
||||||
|
found, runningInfo := duplicatechecker.Container.GetOrSetCronJobRunningInfo(c.Name, currentInfo, c.Period.GetInterval())
|
||||||
|
|
||||||
|
if found {
|
||||||
|
log.Warnf("[cron_job.doRun] job \"%s\" is already running (%s)", c.Name, runningInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentInfo := fmt.Sprintf("ip: %s, startTime: %d", localAddr, time.Now().Unix())
|
err := c.Run()
|
||||||
found, runningInfo := duplicatechecker.Container.GetOrSetCronJobRunningInfo(c.Name, currentInfo, c.Interval)
|
|
||||||
|
|
||||||
if found {
|
|
||||||
log.Warnf("[cron_job.doRun] job \"%s\" is already running (%s)", c.Name, runningInfo)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.Run()
|
|
||||||
|
|
||||||
duplicatechecker.Container.Current.RemoveCronJobRunningInfo(c.Name)
|
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CronJobPeriod represents the cron job period
|
||||||
|
type CronJobPeriod interface {
|
||||||
|
GetInterval() time.Duration
|
||||||
|
ToJobDefinition() gocron.JobDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
// CronJobIntervalPeriod represents the period of execution at intervals
|
||||||
|
type CronJobIntervalPeriod struct {
|
||||||
|
Interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// CronJobFixedHourPeriod represents the period of execution at fixed hour
|
||||||
|
type CronJobFixedHourPeriod struct {
|
||||||
|
Hour uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// CronJobFixedTimePeriod represents the period of execution at fixed time
|
||||||
|
type CronJobFixedTimePeriod struct {
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInterval returns the interval time of the period of CronJobIntervalPeriod
|
||||||
|
func (p CronJobIntervalPeriod) GetInterval() time.Duration {
|
||||||
|
return p.Interval
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJobDefinition returns the gocron job definition of the period of CronJobIntervalPeriod
|
||||||
|
func (p CronJobIntervalPeriod) ToJobDefinition() gocron.JobDefinition {
|
||||||
|
return gocron.DurationJob(p.Interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInterval returns the interval time of the period of CronJobFixedHourPeriod
|
||||||
|
func (p CronJobFixedHourPeriod) GetInterval() time.Duration {
|
||||||
|
return 24 * time.Hour
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJobDefinition returns the gocron job definition of the period of CronJobFixedHourPeriod
|
||||||
|
func (p CronJobFixedHourPeriod) ToJobDefinition() gocron.JobDefinition {
|
||||||
|
return gocron.DailyJob(
|
||||||
|
1,
|
||||||
|
gocron.NewAtTimes(
|
||||||
|
gocron.NewAtTime(uint(p.Hour), 0, 0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInterval returns the interval time of the period of CronJobFixedTimePeriod
|
||||||
|
func (p CronJobFixedTimePeriod) GetInterval() time.Duration {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJobDefinition returns the gocron job definition of the period of CronJobFixedTimePeriod
|
||||||
|
func (p CronJobFixedTimePeriod) ToJobDefinition() gocron.JobDefinition {
|
||||||
|
return gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(p.Time))
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCronJobNextRunTimeWithIntervalPeriod(t *testing.T) {
|
||||||
|
scheduler, err := gocron.NewScheduler(
|
||||||
|
gocron.WithLocation(time.Local),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
job := CronJob{
|
||||||
|
Name: "TestCronJobWithIntervalPeriod",
|
||||||
|
Description: "The test cron job",
|
||||||
|
Period: CronJobIntervalPeriod{
|
||||||
|
Interval: 2*time.Hour + 34*time.Minute + 56*time.Second,
|
||||||
|
},
|
||||||
|
Run: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gocronJob, err := scheduler.NewJob(
|
||||||
|
job.Period.ToJobDefinition(),
|
||||||
|
gocron.NewTask(job.doRun),
|
||||||
|
gocron.WithName(job.Name),
|
||||||
|
gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
scheduler.Start()
|
||||||
|
|
||||||
|
currentTime := time.Now()
|
||||||
|
nextRunTime, err := gocronJob.NextRun()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
expectedNextTime := currentTime.Add(2 * time.Hour).Add(34 * time.Minute).Add(56 * time.Second)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedNextTime.Year(), nextRunTime.Year())
|
||||||
|
assert.Equal(t, expectedNextTime.Month(), nextRunTime.Month())
|
||||||
|
assert.Equal(t, expectedNextTime.Day(), nextRunTime.Day())
|
||||||
|
assert.Equal(t, expectedNextTime.Hour(), nextRunTime.Hour())
|
||||||
|
assert.Equal(t, expectedNextTime.Minute(), nextRunTime.Minute())
|
||||||
|
assert.Equal(t, expectedNextTime.Second(), nextRunTime.Second())
|
||||||
|
|
||||||
|
err = scheduler.Shutdown()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCronJobNextRunTimeWithFixedHourPeriod(t *testing.T) {
|
||||||
|
scheduler, err := gocron.NewScheduler(
|
||||||
|
gocron.WithLocation(time.Local),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
job := CronJob{
|
||||||
|
Name: "TestCronJobWithFixedHourPeriod",
|
||||||
|
Description: "The test cron job",
|
||||||
|
Period: CronJobFixedHourPeriod{
|
||||||
|
Hour: 0,
|
||||||
|
},
|
||||||
|
Run: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gocronJob, err := scheduler.NewJob(
|
||||||
|
job.Period.ToJobDefinition(),
|
||||||
|
gocron.NewTask(job.doRun),
|
||||||
|
gocron.WithName(job.Name),
|
||||||
|
gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
scheduler.Start()
|
||||||
|
|
||||||
|
nextRunTime, err := gocronJob.NextRun()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, tomorrow.Year(), nextRunTime.Year())
|
||||||
|
assert.Equal(t, tomorrow.Month(), nextRunTime.Month())
|
||||||
|
assert.Equal(t, tomorrow.Day(), nextRunTime.Day())
|
||||||
|
assert.Equal(t, 0, nextRunTime.Hour())
|
||||||
|
assert.Equal(t, 0, nextRunTime.Minute())
|
||||||
|
assert.Equal(t, 0, nextRunTime.Second())
|
||||||
|
|
||||||
|
err = scheduler.Shutdown()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCronJobNextRunTimeWithFixedTimePeriod(t *testing.T) {
|
||||||
|
scheduler, err := gocron.NewScheduler(
|
||||||
|
gocron.WithLocation(time.Local),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
expectedTime := time.Now().Add(123456789 * time.Second)
|
||||||
|
|
||||||
|
job := CronJob{
|
||||||
|
Name: "TestCronJobWithFixedTimePeriod",
|
||||||
|
Description: "The test cron job",
|
||||||
|
Period: CronJobFixedTimePeriod{
|
||||||
|
Time: expectedTime,
|
||||||
|
},
|
||||||
|
Run: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gocronJob, err := scheduler.NewJob(
|
||||||
|
job.Period.ToJobDefinition(),
|
||||||
|
gocron.NewTask(job.doRun),
|
||||||
|
gocron.WithName(job.Name),
|
||||||
|
gocron.WithSingletonMode(gocron.LimitModeReschedule),
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
scheduler.Start()
|
||||||
|
|
||||||
|
nextRunTime, err := gocronJob.NextRun()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedTime.Year(), nextRunTime.Year())
|
||||||
|
assert.Equal(t, expectedTime.Month(), nextRunTime.Month())
|
||||||
|
assert.Equal(t, expectedTime.Day(), nextRunTime.Day())
|
||||||
|
assert.Equal(t, expectedTime.Hour(), nextRunTime.Hour())
|
||||||
|
assert.Equal(t, expectedTime.Minute(), nextRunTime.Minute())
|
||||||
|
assert.Equal(t, expectedTime.Second(), nextRunTime.Second())
|
||||||
|
|
||||||
|
err = scheduler.Shutdown()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
package cron
|
package cron
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RemoveExpiredTokensJob represents the cron job which periodically remove expired user tokens from the database
|
||||||
var RemoveExpiredTokensJob = &CronJob{
|
var RemoveExpiredTokensJob = &CronJob{
|
||||||
Name: "RemoveExpiredTokens",
|
Name: "RemoveExpiredTokens",
|
||||||
Description: "Periodically remove expired user tokens from the database.",
|
Description: "Periodically remove expired user tokens from the database.",
|
||||||
Interval: 24 * time.Hour,
|
Period: CronJobFixedHourPeriod{
|
||||||
|
Hour: 0,
|
||||||
|
},
|
||||||
Run: func() error {
|
Run: func() error {
|
||||||
return services.Tokens.DeleteAllExpiredTokens(nil)
|
return services.Tokens.DeleteAllExpiredTokens(nil)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user