Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/git/test/node/gitService.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { suite, test } from 'vitest';
8
import { AdoRepoId, getAdoRepoIdFromFetchUrl, getGithubRepoIdFromFetchUrl, GithubRepoId, normalizeFetchUrl, parseRemoteUrl, toGithubWebUrl } from '../../common/gitService';
9
10
function assertGitIdEquals(a: GithubRepoId | undefined, b: { org: string; repo: string; host?: string } | undefined, message?: string) {
11
assert.strictEqual(a?.org, b?.org, message);
12
assert.strictEqual(a?.repo, b?.repo, message);
13
if (b?.host !== undefined) {
14
assert.strictEqual(a?.host, b.host, message);
15
}
16
}
17
18
suite('parseRemoteUrl', () => {
19
test('Should handle basic https', () => {
20
assert.deepStrictEqual(
21
parseRemoteUrl('https://example.com/owner/repo.git'),
22
{ host: 'example.com', rawHost: 'example.com', path: '/owner/repo.git' });
23
});
24
25
test('Should find full subdomain with https', () => {
26
assert.deepStrictEqual(
27
parseRemoteUrl('https://sub1.sub2.example.com/owner/repo.git'),
28
{ host: 'sub1.sub2.example.com', rawHost: 'sub1.sub2.example.com', path: '/owner/repo.git' });
29
});
30
31
test('Should handle basic Azure dev ops url', () => {
32
assert.deepStrictEqual(
33
parseRemoteUrl('https://[email protected]/test/project/_git/vscode-stuff'),
34
{ host: 'dev.azure.com', rawHost: 'dev.azure.com', path: '/test/project/_git/vscode-stuff' });
35
});
36
37
test('Should handle basic visual studio url', () => {
38
assert.deepStrictEqual(
39
parseRemoteUrl('https://test.visualstudio.com/project/one/_git/two'),
40
{ host: 'test.visualstudio.com', rawHost: 'test.visualstudio.com', path: '/project/one/_git/two' });
41
});
42
43
test('Should strip out ports', () => {
44
assert.deepStrictEqual(
45
parseRemoteUrl('https://example.com:8080/owner/repo.git'),
46
{ host: 'example.com', rawHost: 'example.com', path: '/owner/repo.git' });
47
});
48
49
test('Should handle ssh syntax', () => {
50
assert.deepStrictEqual(
51
parseRemoteUrl('ssh://[email protected]/owner/repo.git'),
52
{ host: 'github.com', rawHost: 'github.com', path: '/owner/repo.git' });
53
});
54
55
test('Should strip user ids', () => {
56
assert.deepStrictEqual(
57
parseRemoteUrl('https://[email protected]/owner/repo.git'),
58
{ host: 'github.com', rawHost: 'github.com', path: '/owner/repo.git' },
59
'https, name only');
60
61
assert.deepStrictEqual(
62
// [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="test credentials")]
63
parseRemoteUrl('https://myname:[email protected]/owner/repo.git'),
64
{ host: 'github.com', rawHost: 'github.com', path: '/owner/repo.git' },
65
'https, with name and PAT');
66
67
assert.deepStrictEqual(
68
parseRemoteUrl('https://[email protected]/owner/repo.git'),
69
{ host: 'github.com', rawHost: 'github.com', path: '/owner/repo.git' },
70
'https, PAT only');
71
72
assert.deepStrictEqual(
73
parseRemoteUrl('ssh://[email protected]/owner/repo.git'),
74
{ host: 'github.com', rawHost: 'github.com', path: '/owner/repo.git' },
75
'ssh, name only');
76
});
77
78
test('Should preserve rawHost for SSH alias hostnames', () => {
79
const result = parseRemoteUrl('[email protected]:owner/repo.git');
80
assert.strictEqual(result?.host, 'github.com');
81
assert.strictEqual(result?.rawHost, 'my-user-name-github.com');
82
});
83
84
test('Should handle GHE SSH shorthand with alias prefix', () => {
85
const result = parseRemoteUrl('[email protected]:sandbox/repo.git');
86
assert.strictEqual(result?.host, 'eu.ghe.com');
87
assert.strictEqual(result?.rawHost, 'msdemo-eu.ghe.com');
88
});
89
});
90
91
suite('getGithubRepoIdFromFetchUrl', () => {
92
test('should return undefined for non-GitHub URLs', () => {
93
const url = 'https://example.com/owner/repo.git';
94
const result = getGithubRepoIdFromFetchUrl(url);
95
assert.strictEqual(result, undefined);
96
});
97
98
test('should return the repo name for git shorthand URLs', () => {
99
assertGitIdEquals(
100
getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git'),
101
{ org: 'owner', repo: 'repo' });
102
103
assertGitIdEquals(
104
getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git'),
105
{ org: 'owner', repo: 'repo' },
106
'ghe url');
107
108
assertGitIdEquals(
109
getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git'),
110
{ org: 'owner', repo: 'repo' },
111
`non 'git' user name`);
112
113
assertGitIdEquals(
114
getGithubRepoIdFromFetchUrl('[email protected]:owner-xyz/some-repo.git'),
115
{ org: 'owner-xyz', repo: 'some-repo' },
116
`non 'git' user name with subdomain alias`);
117
});
118
119
test('should return the repo name for HTTPS URLs', () => {
120
assertGitIdEquals(
121
getGithubRepoIdFromFetchUrl('https://github.com/owner/repo.git'),
122
{ org: 'owner', repo: 'repo' });
123
124
assertGitIdEquals(
125
getGithubRepoIdFromFetchUrl('https://xyz.ghe.com/owner/repo.git'),
126
{ org: 'owner', repo: 'repo' },
127
'ghe url');
128
});
129
130
test('should return the repos with trailing slash', () => {
131
assertGitIdEquals(
132
getGithubRepoIdFromFetchUrl('https://github.com/owner/repo/'),
133
{ org: 'owner', repo: 'repo' });
134
135
assertGitIdEquals(
136
getGithubRepoIdFromFetchUrl('https://github.com/owner/repo.git/'),
137
{ org: 'owner', repo: 'repo' });
138
});
139
140
test('should return the repo name for URLs without .git extension', () => {
141
assertGitIdEquals(
142
getGithubRepoIdFromFetchUrl('https://github.com/owner/repo'),
143
{ org: 'owner', repo: 'repo' });
144
145
assertGitIdEquals(
146
getGithubRepoIdFromFetchUrl('https://github.com/owner/repo/'),
147
{ org: 'owner', repo: 'repo' },
148
'With trailing slash');
149
});
150
151
test('should return the repo name for ssh:// URLs', () => {
152
assertGitIdEquals(
153
getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo.git'),
154
{ org: 'owner', repo: 'repo' });
155
156
assertGitIdEquals(
157
getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo'),
158
{ org: 'owner', repo: 'repo' });
159
160
assertGitIdEquals(
161
getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo.git'),
162
{ org: 'owner', repo: 'repo' },
163
'On ssh.github.com subdomain');
164
165
assertGitIdEquals(
166
getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo.git'),
167
{ org: 'owner', repo: 'repo' },
168
'ghe.com subdomain');
169
170
assertGitIdEquals(
171
getGithubRepoIdFromFetchUrl('ssh://[email protected]:443/owner/repo.git'),
172
{ org: 'owner', repo: 'repo' },
173
'With port');
174
});
175
176
test('should return undefined for invalid GitHub URLs', () => {
177
{
178
const url = 'https://github.com/owner';
179
const result = getGithubRepoIdFromFetchUrl(url);
180
assert.deepStrictEqual(result, undefined);
181
}
182
{
183
const url = 'https://github.com/';
184
const result = getGithubRepoIdFromFetchUrl(url);
185
assert.deepStrictEqual(result, undefined);
186
}
187
});
188
189
test('should return undefined for invalid URLs', () => {
190
const url = 'invalid-url';
191
const result = getGithubRepoIdFromFetchUrl(url);
192
assert.deepStrictEqual(result, undefined);
193
});
194
195
test('should return undefined for unsupported scheme', () => {
196
const url = 'gopher://github.com/owner/repo.git';
197
const result = getGithubRepoIdFromFetchUrl(url);
198
assert.deepStrictEqual(result, undefined);
199
});
200
201
test('should support github url that uses www subdomain', () => {
202
// Likely a mistake but we can parse it easily
203
assertGitIdEquals(
204
getGithubRepoIdFromFetchUrl('https://www.github.com/owner/repo.git'),
205
{ org: 'owner', repo: 'repo' });
206
});
207
208
test('should support github url using http', () => {
209
// This is a mistake but we can parse it easily
210
assertGitIdEquals(
211
getGithubRepoIdFromFetchUrl('http://github.com/owner/repo.git'),
212
{ org: 'owner', repo: 'repo' });
213
});
214
215
test('should support urls with custom user names and PAT in urls', () => {
216
assertGitIdEquals(
217
getGithubRepoIdFromFetchUrl('https://[email protected]/owner/repo.git'),
218
{ org: 'owner', repo: 'repo' },
219
'https, name only');
220
221
assertGitIdEquals(
222
// [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="test credentials")]
223
getGithubRepoIdFromFetchUrl('https://myname:[email protected]/owner/repo.git'),
224
{ org: 'owner', repo: 'repo' },
225
'https, with name and PAT');
226
227
assertGitIdEquals(
228
getGithubRepoIdFromFetchUrl('https://[email protected]/owner/repo.git'),
229
{ org: 'owner', repo: 'repo' },
230
'https, PAT only');
231
232
assertGitIdEquals(
233
getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo.git'),
234
{ org: 'owner', repo: 'repo' },
235
'ssh, name only');
236
});
237
238
test('should support github urls that are likely ssh aliases', () => {
239
assertGitIdEquals(
240
getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git'),
241
{ org: 'owner', repo: 'repo' },
242
'Custom name before github.com');
243
244
assertGitIdEquals(
245
getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git'),
246
{ org: 'owner', repo: 'repo' },
247
'Custom name after github.com');
248
});
249
250
test('should set host to github.com for github.com URLs', () => {
251
const result = getGithubRepoIdFromFetchUrl('https://github.com/owner/repo.git');
252
assert.strictEqual(result?.host, 'github.com');
253
});
254
255
test('should set host to ghe.com subdomain for GHE URLs', () => {
256
const result = getGithubRepoIdFromFetchUrl('https://xyz.ghe.com/owner/repo.git');
257
assert.strictEqual(result?.host, 'xyz.ghe.com');
258
});
259
260
test('should set host to ghe.com subdomain for GHE SSH shorthand URLs', () => {
261
const result = getGithubRepoIdFromFetchUrl('[email protected]:sandbox/repo.git');
262
assert.strictEqual(result?.host, 'msdemo-eu.ghe.com');
263
});
264
265
test('should set host to github.com for SSH alias URLs', () => {
266
const result = getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git');
267
assert.strictEqual(result?.host, 'github.com');
268
});
269
270
test('should set host to github.com for ssh:// github.com URLs', () => {
271
const result = getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo.git');
272
assert.strictEqual(result?.host, 'github.com');
273
});
274
275
test('should set host to ghe.com subdomain for ssh:// ghe URLs', () => {
276
const result = getGithubRepoIdFromFetchUrl('ssh://[email protected]/owner/repo.git');
277
assert.strictEqual(result?.host, 'myco.ghe.com');
278
});
279
280
test('should set host to github.com for http github.com URLs', () => {
281
const result = getGithubRepoIdFromFetchUrl('http://github.com/owner/repo.git');
282
assert.strictEqual(result?.host, 'github.com');
283
});
284
285
test('should set host to github.com for www.github.com URLs', () => {
286
const result = getGithubRepoIdFromFetchUrl('https://www.github.com/owner/repo.git');
287
assert.strictEqual(result?.host, 'github.com');
288
});
289
290
test('should set host to github.com for SSH alias with suffix', () => {
291
const result = getGithubRepoIdFromFetchUrl('[email protected]:owner/repo.git');
292
assert.strictEqual(result?.host, 'github.com');
293
});
294
295
test('should resolve host and org/repo correctly for real-world GHE SSH shorthand', () => {
296
// Real-world scenario: [email protected]:sandbox/repo.git
297
const result = getGithubRepoIdFromFetchUrl('[email protected]:sandbox/repo.git');
298
assert.ok(result, 'Should parse successfully');
299
assert.strictEqual(result.org, 'sandbox');
300
assert.strictEqual(result.repo, 'repo');
301
assert.strictEqual(result.host, 'msdemo-eu.ghe.com');
302
});
303
304
test('should resolve host for GHE HTTPS with credentials', () => {
305
const result = getGithubRepoIdFromFetchUrl('https://[email protected]/org/repo.git');
306
assert.ok(result, 'Should parse successfully');
307
assert.strictEqual(result.host, 'myco.ghe.com');
308
assert.strictEqual(result.org, 'org');
309
assert.strictEqual(result.repo, 'repo');
310
});
311
312
test('should resolve host for GHE HTTPS without .git extension', () => {
313
const result = getGithubRepoIdFromFetchUrl('https://myco.ghe.com/org/repo');
314
assert.ok(result, 'Should parse successfully');
315
assert.strictEqual(result.host, 'myco.ghe.com');
316
});
317
});
318
319
suite('GithubRepoId', () => {
320
test('should default host to github.com', () => {
321
const id = new GithubRepoId('owner', 'repo');
322
assert.strictEqual(id.host, 'github.com');
323
});
324
325
test('should accept custom host', () => {
326
const id = new GithubRepoId('owner', 'repo', 'myco.ghe.com');
327
assert.strictEqual(id.host, 'myco.ghe.com');
328
});
329
330
test('toString should return org/repo without host', () => {
331
const id = new GithubRepoId('Owner', 'Repo', 'myco.ghe.com');
332
assert.strictEqual(id.toString(), 'owner/repo');
333
});
334
335
test('parse should default host to github.com', () => {
336
const id = GithubRepoId.parse('owner/repo');
337
assert.ok(id);
338
assert.strictEqual(id.host, 'github.com');
339
});
340
341
test('parse should return undefined for invalid nwo', () => {
342
assert.strictEqual(GithubRepoId.parse('invalid'), undefined);
343
assert.strictEqual(GithubRepoId.parse('a/b/c'), undefined);
344
assert.strictEqual(GithubRepoId.parse(''), undefined);
345
});
346
347
test('type should be github', () => {
348
const id = new GithubRepoId('owner', 'repo');
349
assert.strictEqual(id.type, 'github');
350
});
351
});
352
353
suite('toGithubWebUrl', () => {
354
test('should return github.com URL for default host', () => {
355
const id = new GithubRepoId('owner', 'repo');
356
assert.strictEqual(toGithubWebUrl(id), 'https://github.com/owner/repo');
357
});
358
359
test('should return GHE URL for custom host', () => {
360
const id = new GithubRepoId('owner', 'repo', 'myco.ghe.com');
361
assert.strictEqual(toGithubWebUrl(id), 'https://myco.ghe.com/owner/repo');
362
});
363
364
test('should preserve case in org and repo', () => {
365
const id = new GithubRepoId('MyOrg', 'MyRepo', 'myco.ghe.com');
366
assert.strictEqual(toGithubWebUrl(id), 'https://myco.ghe.com/MyOrg/MyRepo');
367
});
368
369
test('should handle deeply nested GHE subdomain', () => {
370
const id = new GithubRepoId('org', 'repo', 'dept.company.ghe.com');
371
assert.strictEqual(toGithubWebUrl(id), 'https://dept.company.ghe.com/org/repo');
372
});
373
});
374
375
suite('parseRemoteUrl rawHost', () => {
376
test('should return matching host and rawHost for plain HTTPS URLs', () => {
377
const result = parseRemoteUrl('https://github.com/owner/repo.git');
378
assert.strictEqual(result?.host, 'github.com');
379
assert.strictEqual(result?.rawHost, 'github.com');
380
});
381
382
test('should return matching host and rawHost for GHE HTTPS', () => {
383
const result = parseRemoteUrl('https://myco.ghe.com/owner/repo.git');
384
assert.strictEqual(result?.host, 'myco.ghe.com');
385
assert.strictEqual(result?.rawHost, 'myco.ghe.com');
386
});
387
388
test('should return different host and rawHost for SSH alias prefix', () => {
389
const result = parseRemoteUrl('[email protected]:owner/repo.git');
390
assert.strictEqual(result?.rawHost, 'my-alias-github.com');
391
assert.strictEqual(result?.host, 'github.com');
392
});
393
394
test('should return different host and rawHost for SSH alias suffix', () => {
395
const result = parseRemoteUrl('[email protected]:owner/repo.git');
396
assert.strictEqual(result?.rawHost, 'github.com-my-alias');
397
assert.strictEqual(result?.host, 'github.com');
398
});
399
400
test('should strip port from rawHost', () => {
401
const result = parseRemoteUrl('ssh://[email protected]:443/owner/repo.git');
402
assert.strictEqual(result?.rawHost, 'github.com');
403
});
404
405
test('should strip user from rawHost', () => {
406
const result = parseRemoteUrl('https://[email protected]/owner/repo.git');
407
assert.strictEqual(result?.rawHost, 'myco.ghe.com');
408
});
409
});
410
411
suite('Sanitize Remote Repo Urls', () => {
412
test('Https url is unchanged', () => {
413
const url = 'https://github.com/owner/repo.git';
414
const result = normalizeFetchUrl(url);
415
assert.strictEqual(result, url);
416
});
417
418
test('Http url is converted to https', () => {
419
const url = 'http://github.com/owner/repo.git';
420
const result = normalizeFetchUrl(url);
421
assert.strictEqual(result, 'https://github.com/owner/repo.git');
422
});
423
424
test('Query parameters are removed', () => {
425
const url = 'https://github.com/owner/repo.git';
426
const urlWithQuery = `${url}?query=param`;
427
const result = normalizeFetchUrl(urlWithQuery);
428
assert.strictEqual(result, url);
429
});
430
431
test('SSH is converted to HTTPS', () => {
432
const url = '[email protected]:owner/repo.git';
433
const result = normalizeFetchUrl(url);
434
assert.strictEqual(result, 'https://github.com/owner/repo.git');
435
});
436
437
test('Credentials are removed from HTTPs url', () => {
438
const url = 'https://user:[email protected]/org/repo';
439
const result = normalizeFetchUrl(url);
440
assert.strictEqual(result, 'https://server.com/org/repo');
441
});
442
443
test('SSH ports are normalized and removed', () => {
444
const url = 'ssh://[email protected]:7999/project/repo.git';
445
const result = normalizeFetchUrl(url);
446
assert.strictEqual(result, 'https://bitbucket.company.pl/project/repo.git');
447
});
448
449
test('Bitbucket https urls are properly normalized', () => {
450
const url = 'https://bitbucket.company.pl/scm/project/repo.git';
451
const result = normalizeFetchUrl(url);
452
assert.strictEqual(result, 'https://bitbucket.company.pl/project/repo.git');
453
});
454
455
test('Repos named scm by org foo are not improperly truncated', () => {
456
const url = 'https://github.com/foo/scm.git';
457
const result = normalizeFetchUrl(url);
458
assert.strictEqual(result, 'https://github.com/foo/scm.git');
459
});
460
461
test('Repos named scm by user scm are not improperly truncated', () => {
462
const url = 'https://github.com/scm/scm.git';
463
const result = normalizeFetchUrl(url);
464
assert.strictEqual(result, 'https://github.com/scm/scm.git');
465
});
466
});
467
468
suite('getAdoRepoIdFromFetchUrl', () => {
469
test('should return undefined for non-ADO URLs', () => {
470
assert.strictEqual(
471
getAdoRepoIdFromFetchUrl('https://example.com/owner/repo.git'),
472
undefined);
473
assert.strictEqual(
474
getAdoRepoIdFromFetchUrl('https://github.com/scm/scm.git'),
475
undefined);
476
});
477
478
test('should parse https format', () => {
479
assert.deepStrictEqual(
480
getAdoRepoIdFromFetchUrl('https://dev.azure.com/organization/project/_git/repository'),
481
new AdoRepoId('organization', 'project', 'repository'));
482
});
483
484
test('should parse https format with _optimized', () => {
485
assert.deepStrictEqual(
486
getAdoRepoIdFromFetchUrl('https://dev.azure.com/organization/project/_git/_optimized/repository'),
487
new AdoRepoId('organization', 'project', 'repository'));
488
});
489
490
test('should parse https format with _full', () => {
491
assert.deepStrictEqual(
492
getAdoRepoIdFromFetchUrl('https://dev.azure.com/organization/project/_git/_full/repository'),
493
new AdoRepoId('organization', 'project', 'repository'));
494
});
495
496
test('should parse legacy https format', () => {
497
assert.deepStrictEqual(
498
getAdoRepoIdFromFetchUrl('https://organization.visualstudio.com/project/_git/repository'),
499
new AdoRepoId('organization', 'project', 'repository'));
500
});
501
502
test('should parse legacy https format with _optimized', () => {
503
assert.deepStrictEqual(
504
getAdoRepoIdFromFetchUrl('https://organization.visualstudio.com/project/_git/_optimized/repository'),
505
new AdoRepoId('organization', 'project', 'repository'));
506
});
507
508
test('should parse legacy https format with _full', () => {
509
assert.deepStrictEqual(
510
getAdoRepoIdFromFetchUrl('https://organization.visualstudio.com/project/_git/_full/repository'),
511
new AdoRepoId('organization', 'project', 'repository'));
512
});
513
514
test('should parse ssh format', () => {
515
assert.deepStrictEqual(
516
getAdoRepoIdFromFetchUrl('[email protected]:v3/organization/project/repository'),
517
new AdoRepoId('organization', 'project', 'repository'));
518
});
519
520
test('should parse ssh format with _optimized', () => {
521
assert.deepStrictEqual(
522
getAdoRepoIdFromFetchUrl('[email protected]:v3/organization/project/_optimized/repository'),
523
new AdoRepoId('organization', 'project', 'repository'));
524
});
525
526
test('should parse ssh format with _full', () => {
527
assert.deepStrictEqual(
528
getAdoRepoIdFromFetchUrl('[email protected]:v3/organization/project/_full/repository'),
529
new AdoRepoId('organization', 'project', 'repository'));
530
});
531
532
test('should parse legacy ssh format', () => {
533
assert.deepStrictEqual(
534
getAdoRepoIdFromFetchUrl('[email protected]:v3/organization/project/repository'),
535
new AdoRepoId('organization', 'project', 'repository'));
536
});
537
538
test('should parse legacy ssh format with _optimized', () => {
539
assert.deepStrictEqual(
540
getAdoRepoIdFromFetchUrl('[email protected]:v3/organization/project/_optimized/repository'),
541
new AdoRepoId('organization', 'project', 'repository'));
542
});
543
544
test('should parse legacy ssh format with _full', () => {
545
assert.deepStrictEqual(
546
getAdoRepoIdFromFetchUrl('[email protected]:v3/organization/project/_full/repository'),
547
new AdoRepoId('organization', 'project', 'repository'));
548
});
549
});
550
551