@@ -101,6 +101,112 @@ func TestAzureDevopsWebhook(t *testing.T) {
101101 }
102102}
103103
104+ func TestAzureDevopsWebhookWithURLSpacing (t * testing.T ) {
105+ cases := []struct {
106+ name string
107+ repoURL string
108+ }{
109+ {
110+ name : "legacy URL" ,
111+ repoURL : "https://visualstudio.com/fleet/git%20test/_git/git%20test" ,
112+ },
113+ {
114+ name : "newer URL" ,
115+ repoURL : "https://dev.azure.com/fleet/git%20test/_git/git%20test" ,
116+ },
117+ }
118+
119+ for _ , c := range cases {
120+ t .Run (c .name , func (t * testing.T ) {
121+
122+ const commit = "f00c3a181697bb3829a6462e931c7456bbed557b"
123+ gitRepo := & v1alpha1.GitRepo {
124+ ObjectMeta : metav1.ObjectMeta {
125+ Name : "test" ,
126+ },
127+ Spec : v1alpha1.GitRepoSpec {
128+ Repo : c .repoURL ,
129+ Branch : "main" ,
130+ },
131+ }
132+ scheme := runtime .NewScheme ()
133+ err := v1alpha1 .AddToScheme (scheme )
134+ if err != nil {
135+ t .Errorf ("unexpected error %v" , err )
136+ }
137+ client := cfake .NewClientBuilder ().WithScheme (scheme ).WithRuntimeObjects (gitRepo ).WithStatusSubresource (gitRepo ).Build ()
138+ w := & Webhook {client : client }
139+ w .azureDevops , _ = azuredevops .New ()
140+ jsonBody := []
byte (
`{"subscriptionId":"xxx","notificationId":1,"id":"xxx","eventType":"git.push","publisherId":"tfs","message":{"text":"commit pushed","html":"commit pushed"},"detailedMessage":{"text":"pushed a commit to git test"},"resource":{"commits":[{"commitId":"` + commit + `","author":{"name":"fleet","email":"[email protected] ","date":"2025-08-26T10:16:56Z"},"committer":{"name":"fleet","email":"[email protected] ","date":"2025-08-26T10:16:56Z"},"comment":"test commit","url":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/commits/f00c3a181697bb3829a6462e931c7456bbed557b"}],"refUpdates":[{"name":"refs/heads/main","oldObjectId":"135f8a827edae980466f72eef385881bb4e158d8","newObjectId":"` + commit + `"}],"repository":{"id":"xxx","name":"git test","url":"https://dev.azure.com/fleet/_apis/git/repositories/xxx","project":{"id":"xxx","name":"git test","url":"https://dev.azure.com/fleet/_apis/projects/xxx","state":"wellFormed","visibility":"unchanged","lastUpdateTime":"0001-01-01T00:00:00"},"defaultBranch":"refs/heads/main","remoteUrl":"` + c .
repoURL + `"},"pushedBy":{"displayName":"Fleet","url":"https://spsprodneu1.vssps.visualstudio.com/xxx/_apis/Identities/xxx","_links":{"avatar":{"href":"https://dev.azure.com/fleet/_apis/GraphProfile/MemberAvatars/msa.xxxx"}},"id":"xxx","uniqueName":"[email protected] ","imageUrl":"https://dev.azure.com/fleet/_api/_common/identityImage?id=xxx","descriptor":"xxxx"},"pushId":22,"date":"2025-08-26T10:17:18.735088Z","url":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/pushes/22","_links":{"self":{"href":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/pushes/22"},"repository":{"href":"https://dev.azure.com/fleet/xxx/_apis/git/repositories/xxx"},"commits":{"href":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/pushes/22/commits"},"pusher":{"href":"https://spsprodneu1.vssps.visualstudio.com/xxx/_apis/Identities/xxx"},"refs":{"href":"https://dev.azure.com/fleet/xxx/_apis/git/repositories/xxx/refs/heads/main"}}},"resourceVersion":"1.0","resourceContainers":{"collection":{"id":"xxx","baseUrl":"https://dev.azure.com/fleet/"},"account":{"id":"ec365173-fce3-4dfc-8fc2-950f0b5728b1","baseUrl":"https://dev.azure.com/fleet/"},"project":{"id":"xxx","baseUrl":"https://dev.azure.com/fleet/"}},"createdDate":"2025-08-26T10:17:26.0098694Z"}` )
141+ bodyReader := bytes .NewReader (jsonBody )
142+ req , err := http .NewRequest (http .MethodPost , c .repoURL , bodyReader )
143+ if err != nil {
144+ t .Errorf ("unexpected err %v" , err )
145+ }
146+ h := http.Header {}
147+ h .Add ("X-Vss-Activityid" , "xxx" )
148+ req .Header = h
149+
150+ w .ServeHTTP (& responseWriter {}, req )
151+
152+ updatedGitRepo := & v1alpha1.GitRepo {}
153+ err = client .Get (context .TODO (), types.NamespacedName {Name : gitRepo .Name , Namespace : gitRepo .Namespace }, updatedGitRepo )
154+ if err != nil {
155+ t .Errorf ("unexpected err %v" , err )
156+ }
157+ if updatedGitRepo .Status .WebhookCommit != commit {
158+ t .Errorf ("expected webhook commit %v, but got %v" , commit , updatedGitRepo .Status .WebhookCommit )
159+ }
160+ })
161+ }
162+ }
163+
164+ func TestAzureDevopsWebhookWithURLMatching (t * testing.T ) {
165+ const commit = "f00c3a181697bb3829a6462e931c7456bbed557b"
166+ const repoURL = "https://dev.azure.com/fleet/git-test/_git/git-test"
167+
168+ // Should be matched to repoURL
169+ const remoteURL = "https://fleet.visualstudio.com/git-test/_git/git-test"
170+
171+ gitRepo := & v1alpha1.GitRepo {
172+ ObjectMeta : metav1.ObjectMeta {
173+ Name : "test" ,
174+ },
175+ Spec : v1alpha1.GitRepoSpec {
176+ Repo : repoURL ,
177+ Branch : "main" ,
178+ },
179+ }
180+ scheme := runtime .NewScheme ()
181+ err := v1alpha1 .AddToScheme (scheme )
182+ if err != nil {
183+ t .Errorf ("unexpected error %v" , err )
184+ }
185+ client := cfake .NewClientBuilder ().WithScheme (scheme ).WithRuntimeObjects (gitRepo ).WithStatusSubresource (gitRepo ).Build ()
186+ w := & Webhook {client : client }
187+ w .azureDevops , _ = azuredevops .New ()
188+ jsonBody := []
byte (
`{"subscriptionId":"xxx","notificationId":1,"id":"xxx","eventType":"git.push","publisherId":"tfs","message":{"text":"commit pushed","html":"commit pushed"},"detailedMessage":{"text":"pushed a commit to git test"},"resource":{"commits":[{"commitId":"` + commit + `","author":{"name":"fleet","email":"[email protected] ","date":"2025-08-26T10:16:56Z"},"committer":{"name":"fleet","email":"[email protected] ","date":"2025-08-26T10:16:56Z"},"comment":"test commit","url":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/commits/f00c3a181697bb3829a6462e931c7456bbed557b"}],"refUpdates":[{"name":"refs/heads/main","oldObjectId":"135f8a827edae980466f72eef385881bb4e158d8","newObjectId":"` + commit + `"}],"repository":{"id":"xxx","name":"git test","url":"https://dev.azure.com/fleet/_apis/git/repositories/xxx","project":{"id":"xxx","name":"git test","url":"https://dev.azure.com/fleet/_apis/projects/xxx","state":"wellFormed","visibility":"unchanged","lastUpdateTime":"0001-01-01T00:00:00"},"defaultBranch":"refs/heads/main","remoteUrl":"` + remoteURL + `"},"pushedBy":{"displayName":"Fleet","url":"https://spsprodneu1.vssps.visualstudio.com/xxx/_apis/Identities/xxx","_links":{"avatar":{"href":"https://dev.azure.com/fleet/_apis/GraphProfile/MemberAvatars/msa.xxxx"}},"id":"xxx","uniqueName":"[email protected] ","imageUrl":"https://dev.azure.com/fleet/_api/_common/identityImage?id=xxx","descriptor":"xxxx"},"pushId":22,"date":"2025-08-26T10:17:18.735088Z","url":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/pushes/22","_links":{"self":{"href":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/pushes/22"},"repository":{"href":"https://dev.azure.com/fleet/xxx/_apis/git/repositories/xxx"},"commits":{"href":"https://dev.azure.com/fleet/_apis/git/repositories/xxx/pushes/22/commits"},"pusher":{"href":"https://spsprodneu1.vssps.visualstudio.com/xxx/_apis/Identities/xxx"},"refs":{"href":"https://dev.azure.com/fleet/xxx/_apis/git/repositories/xxx/refs/heads/main"}}},"resourceVersion":"1.0","resourceContainers":{"collection":{"id":"xxx","baseUrl":"https://fleet.visualstudio.com/"},"account":{"id":"ec365173-fce3-4dfc-8fc2-950f0b5728b1","baseUrl":"https://fleet.visualstudio.com/"},"project":{"id":"xxx","baseUrl":"https://fleet.visualstudio.com/"}},"createdDate":"2025-08-26T10:17:26.0098694Z"}` )
189+ bodyReader := bytes .NewReader (jsonBody )
190+ req , err := http .NewRequest (http .MethodPost , repoURL , bodyReader )
191+ if err != nil {
192+ t .Errorf ("unexpected err %v" , err )
193+ }
194+ h := http.Header {}
195+ h .Add ("X-Vss-Activityid" , "xxx" )
196+ req .Header = h
197+
198+ w .ServeHTTP (& responseWriter {}, req )
199+
200+ updatedGitRepo := & v1alpha1.GitRepo {}
201+ err = client .Get (context .TODO (), types.NamespacedName {Name : gitRepo .Name , Namespace : gitRepo .Namespace }, updatedGitRepo )
202+ if err != nil {
203+ t .Errorf ("unexpected err %v" , err )
204+ }
205+ if updatedGitRepo .Status .WebhookCommit != commit {
206+ t .Errorf ("expected webhook commit %v, but got %v" , commit , updatedGitRepo .Status .WebhookCommit )
207+ }
208+ }
209+
104210func TestAzureDevopsWebhookWithSSHURL (t * testing.T ) {
105211 const (
106212 commit = "f00c3a181697bb3829a6462e931c7456bbed557b"
0 commit comments