package scheduler import ( "context" "regexp" "testing" "time" "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/logging" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/aws/aws-sdk-go/service/cloudwatchevents" "github.com/aws/aws-sdk-go/service/cloudwatchevents/cloudwatcheventsiface" ) const ( TargetRegx string = `reinvoke-target-([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}){1}` HandlerRegx string = `reinvoke-handler-([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}){1}` Succeed string = "\u2713" Failed string = "\u2717" Arn string = "arn:aws:lambda:us-east-2:123456789:function:myproject" ) // MockedEvents mocks the call to AWS CloudWatch Events type MockedEvents struct { cloudwatcheventsiface.CloudWatchEventsAPI RuleName string TargetName string } func NewMockEvents() *MockedEvents { return &MockedEvents{} } func (m *MockedEvents) PutRule(in *cloudwatchevents.PutRuleInput) (*cloudwatchevents.PutRuleOutput, error) { m.RuleName = *in.Name return nil, nil } func (m *MockedEvents) PutTargets(in *cloudwatchevents.PutTargetsInput) (*cloudwatchevents.PutTargetsOutput, error) { m.TargetName = *in.Targets[0].Id return nil, nil } func (m *MockedEvents) DeleteRule(in *cloudwatchevents.DeleteRuleInput) (*cloudwatchevents.DeleteRuleOutput, error) { m.RuleName = *in.Name return nil, nil } func (m *MockedEvents) RemoveTargets(in *cloudwatchevents.RemoveTargetsInput) (*cloudwatchevents.RemoveTargetsOutput, error) { m.TargetName = *in.Ids[0] return nil, nil } func TestGenerateOneTimeCronExpression(t *testing.T) { type args struct { minutesFromNow int64 t time.Time } tests := []struct { name string args args want string }{ {"TestCreateOneTimeCronExpression", args{0, time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)}, "cron(34 20 17 11 ? 2009)"}, {"TestCreateOneTimeCronExpression", args{0, time.Date(2001, 5, 25, 11, 04, 14, 651387237, time.UTC)}, "cron(04 11 25 05 ? 2001)"}, {"TestCreateOneTimeCronExpression", args{0, time.Date(2006, 7, 17, 7, 18, 23, 651387237, time.UTC)}, "cron(18 07 17 07 ? 2006)"}, {"TestCreateOneTimeCronExpression", args{0, time.Date(1999, 2, 07, 21, 28, 45, 651387237, time.UTC)}, "cron(28 21 07 02 ? 1999)"}, } for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Logf("\tTest: %d\tWhen checking %q for match status %v", i, tt.name, tt.want) { got := GenerateOneTimeCronExpression(tt.args.minutesFromNow, tt.args.t) if got == tt.want { t.Logf("\t%s\tOneTimeCronExpression match should be (%v).", Succeed, tt.want) } else { t.Errorf("\t%s\tOneTimeCronExpression match should be (%v). : %v", Failed, tt.want, got) } } }) } } func TestCloudWatchSchedulerRescheduleAfterMinutes(t *testing.T) { var cb = `{ string: "Foo"}` ct1, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*time.Duration(1000))) defer cancel() ct2, cancel2 := context.WithDeadline(context.Background(), time.Now().Add(time.Second*time.Duration(16))) defer cancel2() type fields struct { Client cloudwatcheventsiface.CloudWatchEventsAPI } type args struct { ctx context.Context secFromNow int64 callbackContext string } tests := []struct { name string fields fields args args wantErr bool computeLocal bool }{ {"TestCloudWatchScheduler56SecsComputeLocal", fields{NewMockEvents()}, args{lambdacontext.NewContext(ct1, &lambdacontext.LambdaContext{InvokedFunctionArn: Arn}), 15, cb}, false, true}, {"TestCloudWatchScheduler56SecsComputeNotLocal", fields{NewMockEvents()}, args{lambdacontext.NewContext(ct2, &lambdacontext.LambdaContext{InvokedFunctionArn: Arn}), 15, cb}, false, false}, {"TestCloudWatchSchedulerLessThen0", fields{NewMockEvents()}, args{lambdacontext.NewContext(ct1, &lambdacontext.LambdaContext{InvokedFunctionArn: Arn}), -87, cb}, true, false}, } for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Logf("\tTest: %d\tWhen checking %q for error status %v", i, tt.name, tt.wantErr) { c := &Scheduler{ client: tt.fields.Client, logger: logging.New("scheduler: "), } ids, _ := GenerateCloudWatchIDS() cp, err := c.Reschedule(tt.args.ctx, tt.args.secFromNow, tt.args.callbackContext, ids) if err != nil && tt.wantErr { t.Logf("\t%s\tShould be able to make the RescheduleAfterMinutes call.", Succeed) return } if cp.ComputeLocal == tt.computeLocal { t.Logf("\t%s\tCompute Local should be (%v).", Succeed, tt.computeLocal) } else { t.Errorf("\t%s\tCompute Local should be (%v). : Value:%v", Failed, tt.computeLocal, cp.ComputeLocal) return } } }) } } func TestCloudWatchSchedulerCleanupCloudWatchEvents(t *testing.T) { type fields struct { Client cloudwatcheventsiface.CloudWatchEventsAPI } type args struct { cloudWatchEventsRuleName string cloudWatchEventsTargetID string } tests := []struct { name string fields fields args args wantErr bool }{ {"TestCloudWatchRemove", fields{NewMockEvents()}, args{"reinvoke-handler-c51d7ba5-8eed-4226-99a6-6743f1169688", "reinvoke-target-c51d7ba5-8eed-4226-99a6-6743f1169688"}, false}, {"TestCloudWatchRemoveBlankCloudWatchEventsRuleName", fields{NewMockEvents()}, args{"", "reinvoke-target-c51d7ba5-8eed-4226-99a6-6743f1169688"}, true}, {"TestCloudWatchRemoveBlankcloudWatchEventsTargetID", fields{NewMockEvents()}, args{"reinvoke-handler-c51d7ba5-8eed-4226-99a6-6743f1169688", ""}, true}, } for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Logf("\tTest: %d\tWhen checking %q for error status %v", i, tt.name, tt.wantErr) { c := &Scheduler{ client: tt.fields.Client, logger: logging.New("scheduler: "), } if err := c.CleanupEvents(tt.args.cloudWatchEventsRuleName, tt.args.cloudWatchEventsTargetID); (err != nil) != tt.wantErr { t.Errorf("\t%s\tShould be able to make the cloudWatchEventsRuleName call : %v", Failed, err) return } t.Logf("\t%s\tShould be able to make the cloudWatchEventsRuleName call.", Succeed) } }) } } func TestGenerateCloudWatchIDS(t *testing.T) { tests := []struct { name string WantRegxRuleName string WantRegxTargetName string WantRuleMatch bool WantTargetMatch bool wantErr bool }{ {"Test Rule and Target pattern", HandlerRegx, TargetRegx, true, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := GenerateCloudWatchIDS() if (err != nil) != tt.wantErr { t.Errorf("GenerateCloudWatchIDS() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr == false { matchedRule, err := regexp.Match(tt.WantRegxRuleName, []byte(got.Handler)) if (err != nil) != tt.wantErr { t.Errorf("\t%s\tShould be able to make the Match call : %v", Failed, err) return } t.Logf("\t%s\tShould be able to make the Matchcall.", Succeed) if matchedRule == tt.WantRuleMatch { t.Logf("\t%s\tRule match should be (%v).", Succeed, tt.WantRuleMatch) } else { t.Errorf("\t%s\tRule match should be (%v). : %v", Failed, tt.WantRuleMatch, matchedRule) } matchedTarget, err := regexp.Match(tt.WantRegxTargetName, []byte(got.Target)) if (err != nil) != tt.wantErr { t.Errorf("\t%s\tShould be able to make the Match call : %v", Failed, err) return } t.Logf("\t%s\tShould be able to make the Matchcall.", Succeed) if matchedTarget == tt.WantTargetMatch { t.Logf("\t%s\tTarget match should be (%v).", Succeed, tt.WantTargetMatch) } else { t.Errorf("\t%s\tTarget match should be (%v). : %v", Failed, tt.WantRegxTargetName, matchedTarget) } } }) } }