Skip to content

Commit 2038e9f

Browse files
test(webhook): add tests for refresh and hydration scenarios
Hydration with sourceHydrator: syncSource refresh - Direct sync from hydrated branch without re-hydration drySource with annotation - Hydration triggered only when changed files match manifest-generate-paths drySource without annotation - Hydration triggered when changed files are within drySource path (default fallback) Standard applications (no sourceHydrator): With annotation - Refresh only when changed files match manifest-generate-paths Without annotation - Always refresh on any change (default behavior) Signed-off-by: Codey Jenkins <[email protected]>
1 parent 2133388 commit 2038e9f

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

util/webhook/webhook_test.go

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,354 @@ func TestGitHubCommitEvent_SyncSourceRefresh_FileFiltering(t *testing.T) {
548548
}
549549
}
550550

551+
// TestGitHubCommitEvent_Hydration_DrySource_WithAnnotation tests that drySource webhooks
552+
// with manifest-generate-paths annotation filter files based on annotation paths
553+
func TestGitHubCommitEvent_Hydration_DrySource_WithAnnotation(t *testing.T) {
554+
hook := test.NewGlobal()
555+
var patched bool
556+
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
557+
patchAction := action.(kubetesting.PatchAction)
558+
assert.Equal(t, "app-with-annotation", patchAction.GetName())
559+
patched = true
560+
return true, nil, nil
561+
}
562+
563+
// The test payload changes files under ksapps/ directory
564+
tests := []struct {
565+
name string
566+
annotation string
567+
drySourcePath string
568+
expectedRefresh bool
569+
}{
570+
{
571+
name: "annotation matches changed files - should hydrate",
572+
annotation: ".",
573+
drySourcePath: "ksapps",
574+
expectedRefresh: true,
575+
},
576+
{
577+
name: "annotation does not match changed files - should not hydrate",
578+
annotation: ".",
579+
drySourcePath: "helm-charts",
580+
expectedRefresh: false,
581+
},
582+
{
583+
name: "annotation with relative path matches - should hydrate",
584+
annotation: ".;../shared",
585+
drySourcePath: "ksapps",
586+
expectedRefresh: true,
587+
},
588+
}
589+
590+
for _, tt := range tests {
591+
t.Run(tt.name, func(t *testing.T) {
592+
patched = false
593+
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
594+
ObjectMeta: metav1.ObjectMeta{
595+
Name: "app-with-annotation",
596+
Namespace: "argocd",
597+
Annotations: map[string]string{
598+
"argocd.argoproj.io/manifest-generate-paths": tt.annotation,
599+
},
600+
},
601+
Spec: v1alpha1.ApplicationSpec{
602+
SourceHydrator: &v1alpha1.SourceHydrator{
603+
DrySource: v1alpha1.DrySource{
604+
RepoURL: "https://github.com/jessesuen/test-repo",
605+
TargetRevision: "HEAD", // Matches master branch from webhook event
606+
Path: tt.drySourcePath,
607+
},
608+
SyncSource: v1alpha1.SyncSource{
609+
TargetBranch: "environments/dev",
610+
Path: "hydrated",
611+
},
612+
},
613+
},
614+
})
615+
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
616+
req.Header.Set("X-GitHub-Event", "push")
617+
// Use main branch event for drySource testing (matches drySource.TargetRevision)
618+
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
619+
require.NoError(t, err)
620+
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
621+
w := httptest.NewRecorder()
622+
h.Handler(w, req)
623+
close(h.queue)
624+
h.Wait()
625+
assert.Equal(t, http.StatusOK, w.Code)
626+
assert.Equal(t, tt.expectedRefresh, patched)
627+
628+
logMessages := make([]string, 0, len(hook.Entries))
629+
for _, entry := range hook.Entries {
630+
logMessages = append(logMessages, entry.Message)
631+
}
632+
633+
if tt.expectedRefresh {
634+
assert.Contains(t, logMessages, "webhook trigger refresh app to hydrate 'app-with-annotation'")
635+
} else {
636+
assert.NotContains(t, logMessages, "webhook trigger refresh app to hydrate 'app-with-annotation'")
637+
}
638+
hook.Reset()
639+
})
640+
}
641+
}
642+
643+
// TestGitHubCommitEvent_Hydration_DrySource_NoAnnotation tests that drySource webhooks
644+
// without manifest-generate-paths annotation use the entire drySource path as default
645+
func TestGitHubCommitEvent_Hydration_DrySource_NoAnnotation(t *testing.T) {
646+
hook := test.NewGlobal()
647+
var patched bool
648+
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
649+
patchAction := action.(kubetesting.PatchAction)
650+
assert.Equal(t, "app-no-annotation", patchAction.GetName())
651+
patched = true
652+
return true, nil, nil
653+
}
654+
655+
// The test payload changes files under ksapps/ directory
656+
tests := []struct {
657+
name string
658+
drySourcePath string
659+
expectedRefresh bool
660+
}{
661+
{
662+
name: "drySource path matches changed files - should hydrate",
663+
drySourcePath: "ksapps",
664+
expectedRefresh: true,
665+
},
666+
{
667+
name: "drySource path does not match changed files - should not hydrate",
668+
drySourcePath: "helm-charts",
669+
expectedRefresh: false,
670+
},
671+
{
672+
name: "drySource at root path - should hydrate",
673+
drySourcePath: ".",
674+
expectedRefresh: true,
675+
},
676+
}
677+
678+
for _, tt := range tests {
679+
t.Run(tt.name, func(t *testing.T) {
680+
patched = false
681+
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
682+
ObjectMeta: metav1.ObjectMeta{
683+
Name: "app-no-annotation",
684+
Namespace: "argocd",
685+
// No manifest-generate-paths annotation
686+
},
687+
Spec: v1alpha1.ApplicationSpec{
688+
SourceHydrator: &v1alpha1.SourceHydrator{
689+
DrySource: v1alpha1.DrySource{
690+
RepoURL: "https://github.com/jessesuen/test-repo",
691+
TargetRevision: "HEAD", // Matches master branch from webhook event
692+
Path: tt.drySourcePath,
693+
},
694+
SyncSource: v1alpha1.SyncSource{
695+
TargetBranch: "environments/dev",
696+
Path: "hydrated",
697+
},
698+
},
699+
},
700+
})
701+
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
702+
req.Header.Set("X-GitHub-Event", "push")
703+
// Use main branch event for drySource testing (matches drySource.TargetRevision)
704+
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
705+
require.NoError(t, err)
706+
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
707+
w := httptest.NewRecorder()
708+
h.Handler(w, req)
709+
close(h.queue)
710+
h.Wait()
711+
assert.Equal(t, http.StatusOK, w.Code)
712+
assert.Equal(t, tt.expectedRefresh, patched)
713+
714+
logMessages := make([]string, 0, len(hook.Entries))
715+
for _, entry := range hook.Entries {
716+
logMessages = append(logMessages, entry.Message)
717+
}
718+
719+
if tt.expectedRefresh {
720+
assert.Contains(t, logMessages, "webhook trigger refresh app to hydrate 'app-no-annotation'")
721+
} else {
722+
assert.NotContains(t, logMessages, "webhook trigger refresh app to hydrate 'app-no-annotation'")
723+
}
724+
hook.Reset()
725+
})
726+
}
727+
}
728+
729+
// TestGitHubCommitEvent_Standard_WithAnnotation tests that standard apps (no hydration)
730+
// with manifest-generate-paths annotation only refresh when changed files match annotation paths
731+
func TestGitHubCommitEvent_Standard_WithAnnotation(t *testing.T) {
732+
hook := test.NewGlobal()
733+
var patched bool
734+
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
735+
patchAction := action.(kubetesting.PatchAction)
736+
assert.Equal(t, "standard-app-with-annotation", patchAction.GetName())
737+
patched = true
738+
return true, nil, nil
739+
}
740+
741+
// The test payload changes files under ksapps/ directory
742+
tests := []struct {
743+
name string
744+
annotation string
745+
sourcePath string
746+
expectedRefresh bool
747+
}{
748+
{
749+
name: "annotation matches changed files - should refresh",
750+
annotation: ".",
751+
sourcePath: "ksapps",
752+
expectedRefresh: true,
753+
},
754+
{
755+
name: "annotation does not match changed files - should not refresh",
756+
annotation: ".",
757+
sourcePath: "helm-charts",
758+
expectedRefresh: false,
759+
},
760+
{
761+
name: "annotation with multiple paths, one matches - should refresh",
762+
annotation: ".;../other",
763+
sourcePath: "ksapps",
764+
expectedRefresh: true,
765+
},
766+
{
767+
name: "annotation at root matches all - should refresh",
768+
annotation: ".",
769+
sourcePath: ".",
770+
expectedRefresh: true,
771+
},
772+
}
773+
774+
for _, tt := range tests {
775+
t.Run(tt.name, func(t *testing.T) {
776+
patched = false
777+
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
778+
ObjectMeta: metav1.ObjectMeta{
779+
Name: "standard-app-with-annotation",
780+
Namespace: "argocd",
781+
Annotations: map[string]string{
782+
"argocd.argoproj.io/manifest-generate-paths": tt.annotation,
783+
},
784+
},
785+
Spec: v1alpha1.ApplicationSpec{
786+
Sources: v1alpha1.ApplicationSources{
787+
{
788+
RepoURL: "https://github.com/jessesuen/test-repo",
789+
Path: tt.sourcePath,
790+
TargetRevision: "HEAD", // Matches the master branch from the webhook event
791+
},
792+
},
793+
},
794+
})
795+
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
796+
req.Header.Set("X-GitHub-Event", "push")
797+
// Use main branch event for standard app testing (matches source targetRevision: HEAD/master)
798+
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
799+
require.NoError(t, err)
800+
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
801+
w := httptest.NewRecorder()
802+
h.Handler(w, req)
803+
close(h.queue)
804+
h.Wait()
805+
assert.Equal(t, http.StatusOK, w.Code)
806+
assert.Equal(t, tt.expectedRefresh, patched)
807+
808+
logMessages := make([]string, 0, len(hook.Entries))
809+
for _, entry := range hook.Entries {
810+
logMessages = append(logMessages, entry.Message)
811+
}
812+
813+
if tt.expectedRefresh {
814+
assert.Contains(t, logMessages, "Requested app 'standard-app-with-annotation' refresh")
815+
} else {
816+
assert.NotContains(t, logMessages, "Requested app 'standard-app-with-annotation' refresh")
817+
}
818+
hook.Reset()
819+
})
820+
}
821+
}
822+
823+
// TestGitHubCommitEvent_Standard_NoAnnotation tests that standard apps (no hydration)
824+
// without manifest-generate-paths annotation always refresh on any change (default behavior)
825+
func TestGitHubCommitEvent_Standard_NoAnnotation(t *testing.T) {
826+
hook := test.NewGlobal()
827+
var patched bool
828+
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
829+
patchAction := action.(kubetesting.PatchAction)
830+
assert.Equal(t, "standard-app-no-annotation", patchAction.GetName())
831+
patched = true
832+
return true, nil, nil
833+
}
834+
835+
// Test that regardless of which files change or which path is configured,
836+
// the app always refreshes when no annotation is present (default behavior)
837+
tests := []struct {
838+
name string
839+
sourcePath string
840+
}{
841+
{
842+
name: "source path matches changed files - should refresh",
843+
sourcePath: "ksapps",
844+
},
845+
{
846+
name: "source path does not match changed files - should still refresh",
847+
sourcePath: "helm-charts",
848+
},
849+
{
850+
name: "source at root - should refresh",
851+
sourcePath: ".",
852+
},
853+
}
854+
855+
for _, tt := range tests {
856+
t.Run(tt.name, func(t *testing.T) {
857+
patched = false
858+
h := NewMockHandler(&reactorDef{"patch", "applications", reaction}, []string{}, &v1alpha1.Application{
859+
ObjectMeta: metav1.ObjectMeta{
860+
Name: "standard-app-no-annotation",
861+
Namespace: "argocd",
862+
// No manifest-generate-paths annotation
863+
},
864+
Spec: v1alpha1.ApplicationSpec{
865+
Sources: v1alpha1.ApplicationSources{
866+
{
867+
RepoURL: "https://github.com/jessesuen/test-repo",
868+
Path: tt.sourcePath,
869+
TargetRevision: "HEAD", // Matches the master branch from the webhook event
870+
},
871+
},
872+
},
873+
})
874+
req := httptest.NewRequest(http.MethodPost, "/api/webhook", http.NoBody)
875+
req.Header.Set("X-GitHub-Event", "push")
876+
// Use main branch event for standard app testing (matches source targetRevision: HEAD/master)
877+
eventJSON, err := os.ReadFile("testdata/github-commit-event.json")
878+
require.NoError(t, err)
879+
req.Body = io.NopCloser(bytes.NewReader(eventJSON))
880+
w := httptest.NewRecorder()
881+
h.Handler(w, req)
882+
close(h.queue)
883+
h.Wait()
884+
assert.Equal(t, http.StatusOK, w.Code)
885+
// Should always refresh regardless of path
886+
assert.True(t, patched, "expected app to refresh but it didn't")
887+
888+
logMessages := make([]string, 0, len(hook.Entries))
889+
for _, entry := range hook.Entries {
890+
logMessages = append(logMessages, entry.Message)
891+
}
892+
893+
assert.Contains(t, logMessages, "Requested app 'standard-app-no-annotation' refresh")
894+
hook.Reset()
895+
})
896+
}
897+
}
898+
551899
func TestGitHubTagEvent(t *testing.T) {
552900
hook := test.NewGlobal()
553901
h := NewMockHandler(nil, []string{})

0 commit comments

Comments
 (0)