You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

urls-test.ts 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { searchParamsToQuery } from '~sonar-aligned/helpers/router';
  21. import { queryToSearchString } from '~sonar-aligned/helpers/urls';
  22. import { ComponentQualifier } from '~sonar-aligned/types/component';
  23. import { AlmKeys } from '../../types/alm-settings';
  24. import { IssueType } from '../../types/issues';
  25. import { MeasurePageView } from '../../types/measures';
  26. import { mockBranch, mockMainBranch, mockPullRequest } from '../mocks/branch-like';
  27. import { mockLocation } from '../testMocks';
  28. import {
  29. CodeScope,
  30. convertGithubApiUrlToLink,
  31. convertToTo,
  32. getComponentAdminUrl,
  33. getComponentDrilldownUrl,
  34. getComponentDrilldownUrlWithSelection,
  35. getComponentOverviewUrl,
  36. getCreateProjectModeLocation,
  37. getDeprecatedActiveRulesUrl,
  38. getGlobalSettingsUrl,
  39. getIssuesUrl,
  40. getPathUrlAsString,
  41. getProjectSettingsUrl,
  42. getQualityGateUrl,
  43. getQualityGatesUrl,
  44. getReturnUrl,
  45. isRelativeUrl,
  46. stripTrailingSlash,
  47. } from '../urls';
  48. const SIMPLE_COMPONENT_KEY = 'sonarqube';
  49. const COMPLEX_COMPONENT_KEY = 'org.sonarsource.sonarqube:sonarqube';
  50. const METRIC = 'coverage';
  51. const COMPLEX_COMPONENT_KEY_ENCODED = encodeURIComponent(COMPLEX_COMPONENT_KEY);
  52. describe('#convertGithubApiUrlToLink', () => {
  53. it('should correctly convert a GitHub API URL to a Web URL', () => {
  54. expect(convertGithubApiUrlToLink('https://api.github.com')).toBe('https://github.com');
  55. expect(convertGithubApiUrlToLink('https://company.github.com/api/v3')).toBe(
  56. 'https://company.github.com',
  57. );
  58. });
  59. });
  60. describe('#stripTrailingSlash', () => {
  61. it('should correctly strip trailing slashes from any URL', () => {
  62. expect(stripTrailingSlash('https://example.com/')).toBe('https://example.com');
  63. expect(convertGithubApiUrlToLink('https://example.com')).toBe('https://example.com');
  64. });
  65. });
  66. describe('getComponentAdminUrl', () => {
  67. it.each([
  68. [
  69. 'Portfolio',
  70. ComponentQualifier.Portfolio,
  71. { pathname: '/project/admin/extension/governance/console', search: '?id=key&qualifier=VW' },
  72. ],
  73. [
  74. 'Application',
  75. ComponentQualifier.Application,
  76. {
  77. pathname: '/project/admin/extension/developer-server/application-console',
  78. search: '?id=key',
  79. },
  80. ],
  81. ['Project', ComponentQualifier.Project, { pathname: '/dashboard', search: '?id=key' }],
  82. ])('should work for %s', (_qualifierName, qualifier, result) => {
  83. expect(getComponentAdminUrl('key', qualifier)).toEqual(result);
  84. });
  85. });
  86. describe('#getComponentOverviewUrl', () => {
  87. it('should return a portfolio url for a portfolio', () => {
  88. expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual(
  89. expect.objectContaining({
  90. pathname: '/portfolio',
  91. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }),
  92. }),
  93. );
  94. });
  95. it('should return a portfolio url for a subportfolio', () => {
  96. expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual(
  97. expect.objectContaining({
  98. pathname: '/portfolio',
  99. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }),
  100. }),
  101. );
  102. });
  103. it('should return a dashboard url for a project', () => {
  104. expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual(
  105. expect.objectContaining({
  106. pathname: '/dashboard',
  107. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }),
  108. }),
  109. );
  110. });
  111. it('should return correct dashboard url for a project when navigating from new code', () => {
  112. expect(
  113. getComponentOverviewUrl(
  114. SIMPLE_COMPONENT_KEY,
  115. ComponentQualifier.Project,
  116. undefined,
  117. CodeScope.New,
  118. ),
  119. ).toEqual(
  120. expect.objectContaining({
  121. pathname: '/dashboard',
  122. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, code_scope: 'new' }),
  123. }),
  124. );
  125. });
  126. it('should return correct dashboard url for a project when navigating from overall code', () => {
  127. expect(
  128. getComponentOverviewUrl(
  129. SIMPLE_COMPONENT_KEY,
  130. ComponentQualifier.Project,
  131. undefined,
  132. CodeScope.Overall,
  133. ),
  134. ).toEqual(
  135. expect.objectContaining({
  136. pathname: '/dashboard',
  137. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, code_scope: 'overall' }),
  138. }),
  139. );
  140. });
  141. it('should return a dashboard url for an app', () => {
  142. expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual(
  143. expect.objectContaining({
  144. pathname: '/dashboard',
  145. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }),
  146. }),
  147. );
  148. });
  149. });
  150. describe('#getComponentDrilldownUrl', () => {
  151. it('should return component drilldown url', () => {
  152. expect(
  153. getComponentDrilldownUrl({ componentKey: SIMPLE_COMPONENT_KEY, metric: METRIC }),
  154. ).toEqual(
  155. expect.objectContaining({
  156. pathname: '/component_measures',
  157. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }),
  158. }),
  159. );
  160. });
  161. it('should not encode component key', () => {
  162. expect(
  163. getComponentDrilldownUrl({ componentKey: COMPLEX_COMPONENT_KEY, metric: METRIC }),
  164. ).toEqual(
  165. expect.objectContaining({
  166. pathname: '/component_measures',
  167. search: queryToSearchString({ id: COMPLEX_COMPONENT_KEY, metric: METRIC }),
  168. }),
  169. );
  170. });
  171. it('should add asc param only when its list view', () => {
  172. expect(
  173. getComponentDrilldownUrl({ componentKey: SIMPLE_COMPONENT_KEY, metric: METRIC, asc: false }),
  174. ).toEqual(
  175. expect.objectContaining({
  176. pathname: '/component_measures',
  177. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY, metric: METRIC }),
  178. }),
  179. );
  180. expect(
  181. getComponentDrilldownUrl({
  182. componentKey: SIMPLE_COMPONENT_KEY,
  183. metric: METRIC,
  184. listView: true,
  185. asc: false,
  186. }),
  187. ).toEqual(
  188. expect.objectContaining({
  189. pathname: '/component_measures',
  190. search: queryToSearchString({
  191. id: SIMPLE_COMPONENT_KEY,
  192. metric: METRIC,
  193. view: 'list',
  194. asc: 'false',
  195. }),
  196. }),
  197. );
  198. });
  199. });
  200. describe('#getComponentDrilldownUrlWithSelection', () => {
  201. it('should return component drilldown url with selection', () => {
  202. expect(
  203. getComponentDrilldownUrlWithSelection(SIMPLE_COMPONENT_KEY, COMPLEX_COMPONENT_KEY, METRIC),
  204. ).toEqual(
  205. expect.objectContaining({
  206. pathname: '/component_measures',
  207. search: queryToSearchString({
  208. id: SIMPLE_COMPONENT_KEY,
  209. metric: METRIC,
  210. selected: COMPLEX_COMPONENT_KEY,
  211. }),
  212. }),
  213. );
  214. });
  215. it('should return component drilldown url with branchLike', () => {
  216. expect(
  217. getComponentDrilldownUrlWithSelection(
  218. SIMPLE_COMPONENT_KEY,
  219. COMPLEX_COMPONENT_KEY,
  220. METRIC,
  221. mockBranch({ name: 'foo' }),
  222. ),
  223. ).toEqual(
  224. expect.objectContaining({
  225. pathname: '/component_measures',
  226. search: queryToSearchString({
  227. id: SIMPLE_COMPONENT_KEY,
  228. metric: METRIC,
  229. branch: 'foo',
  230. selected: COMPLEX_COMPONENT_KEY,
  231. }),
  232. }),
  233. );
  234. });
  235. it('should return component drilldown url with view parameter', () => {
  236. expect(
  237. getComponentDrilldownUrlWithSelection(
  238. SIMPLE_COMPONENT_KEY,
  239. COMPLEX_COMPONENT_KEY,
  240. METRIC,
  241. undefined,
  242. MeasurePageView.list,
  243. ),
  244. ).toEqual(
  245. expect.objectContaining({
  246. pathname: '/component_measures',
  247. search: queryToSearchString({
  248. id: SIMPLE_COMPONENT_KEY,
  249. metric: METRIC,
  250. view: MeasurePageView.list,
  251. selected: COMPLEX_COMPONENT_KEY,
  252. }),
  253. }),
  254. );
  255. expect(
  256. getComponentDrilldownUrlWithSelection(
  257. SIMPLE_COMPONENT_KEY,
  258. COMPLEX_COMPONENT_KEY,
  259. METRIC,
  260. mockMainBranch(),
  261. MeasurePageView.treemap,
  262. ),
  263. ).toEqual(
  264. expect.objectContaining({
  265. pathname: '/component_measures',
  266. search: queryToSearchString({
  267. id: SIMPLE_COMPONENT_KEY,
  268. metric: METRIC,
  269. view: MeasurePageView.treemap,
  270. selected: COMPLEX_COMPONENT_KEY,
  271. }),
  272. }),
  273. );
  274. expect(
  275. getComponentDrilldownUrlWithSelection(
  276. SIMPLE_COMPONENT_KEY,
  277. COMPLEX_COMPONENT_KEY,
  278. METRIC,
  279. mockPullRequest({ key: '1' }),
  280. MeasurePageView.tree,
  281. ),
  282. ).toEqual(
  283. expect.objectContaining({
  284. pathname: '/component_measures',
  285. search: queryToSearchString({
  286. id: SIMPLE_COMPONENT_KEY,
  287. metric: METRIC,
  288. pullRequest: '1',
  289. selected: COMPLEX_COMPONENT_KEY,
  290. }),
  291. }),
  292. );
  293. });
  294. });
  295. describe('getDeprecatedActiveRulesUrl', () => {
  296. it('should include query params', () => {
  297. expect(getDeprecatedActiveRulesUrl({ languages: 'js' })).toEqual({
  298. pathname: '/coding_rules',
  299. search: '?languages=js&activation=true&statuses=DEPRECATED',
  300. });
  301. });
  302. it('should handle empty query', () => {
  303. expect(getDeprecatedActiveRulesUrl()).toEqual({
  304. pathname: '/coding_rules',
  305. search: '?activation=true&statuses=DEPRECATED',
  306. });
  307. });
  308. });
  309. describe('#getQualityGate(s)Url', () => {
  310. it('should work as expected', () => {
  311. expect(getQualityGatesUrl()).toEqual({ pathname: '/quality_gates' });
  312. expect(getQualityGateUrl('bar')).toEqual({ pathname: '/quality_gates/show/bar' });
  313. });
  314. });
  315. describe('#getIssuesUrl', () => {
  316. it('should work as expected', () => {
  317. const type = IssueType.Bug;
  318. expect(getIssuesUrl({ type })).toEqual({
  319. pathname: '/issues',
  320. search: queryToSearchString({ type }),
  321. });
  322. });
  323. });
  324. describe('#getGlobalSettingsUrl', () => {
  325. it('should work as expected', () => {
  326. expect(getGlobalSettingsUrl('foo')).toEqual({
  327. pathname: '/admin/settings',
  328. search: queryToSearchString({ category: 'foo' }),
  329. });
  330. expect(getGlobalSettingsUrl('foo', { alm: AlmKeys.GitHub })).toEqual({
  331. pathname: '/admin/settings',
  332. search: queryToSearchString({ category: 'foo', alm: AlmKeys.GitHub }),
  333. });
  334. });
  335. });
  336. describe('#getProjectSettingsUrl', () => {
  337. it('should work as expected', () => {
  338. expect(getProjectSettingsUrl('foo')).toEqual({
  339. pathname: '/project/settings',
  340. search: queryToSearchString({ id: 'foo' }),
  341. });
  342. expect(getProjectSettingsUrl('foo', 'bar')).toEqual({
  343. pathname: '/project/settings',
  344. search: queryToSearchString({ id: 'foo', category: 'bar' }),
  345. });
  346. });
  347. });
  348. describe('#getPathUrlAsString', () => {
  349. it('should return component url', () => {
  350. expect(
  351. getPathUrlAsString({
  352. pathname: '/dashboard',
  353. search: queryToSearchString({ id: SIMPLE_COMPONENT_KEY }),
  354. }),
  355. ).toBe('/dashboard?id=' + SIMPLE_COMPONENT_KEY);
  356. });
  357. it('should encode component key', () => {
  358. expect(
  359. getPathUrlAsString({
  360. pathname: '/dashboard',
  361. search: queryToSearchString({ id: COMPLEX_COMPONENT_KEY }),
  362. }),
  363. ).toBe('/dashboard?id=' + COMPLEX_COMPONENT_KEY_ENCODED);
  364. });
  365. it('should handle partial arguments', () => {
  366. expect(getPathUrlAsString({}, true)).toBe('/');
  367. });
  368. });
  369. describe('#getReturnUrl', () => {
  370. it('should get the return url', () => {
  371. expect(getReturnUrl({ query: { return_to: '/test' } })).toBe('/test');
  372. expect(getReturnUrl({ query: { return_to: 'http://www.google.com' } })).toBe('/');
  373. expect(getReturnUrl({})).toBe('/');
  374. });
  375. });
  376. describe('#isRelativeUrl', () => {
  377. it('should check a relative url', () => {
  378. expect(isRelativeUrl('/test')).toBe(true);
  379. expect(isRelativeUrl('http://www.google.com')).toBe(false);
  380. expect(isRelativeUrl('javascript:alert("test")')).toBe(false);
  381. expect(isRelativeUrl('\\test')).toBe(false);
  382. expect(isRelativeUrl('//test')).toBe(false);
  383. });
  384. });
  385. describe('#getHostUrl', () => {
  386. beforeEach(() => {
  387. jest.resetModules();
  388. });
  389. it('should return host url on client side', () => {
  390. jest.mock('../system', () => ({
  391. getBaseUrl: () => '',
  392. }));
  393. const mockedUrls = require('../urls');
  394. expect(mockedUrls.getHostUrl()).toBe('http://localhost');
  395. });
  396. });
  397. describe('searchParamsToQuery', () => {
  398. it('should handle arrays and single params', () => {
  399. const searchParams = new URLSearchParams([
  400. ['a', 'v1'],
  401. ['a', 'v2'],
  402. ['b', 'awesome'],
  403. ['a', 'v3'],
  404. ]);
  405. const result = searchParamsToQuery(searchParams);
  406. expect(result).toEqual({ a: ['v1', 'v2', 'v3'], b: 'awesome' });
  407. });
  408. });
  409. describe('convertToTo', () => {
  410. it('should handle locations with a query', () => {
  411. expect(convertToTo(mockLocation({ pathname: '/account', query: { id: 1 } }))).toEqual({
  412. pathname: '/account',
  413. search: '?id=1',
  414. });
  415. });
  416. it('should forward strings', () => {
  417. expect(convertToTo('/whatever')).toBe('/whatever');
  418. });
  419. });
  420. describe('#get import devops config URL', () => {
  421. it('should work as expected', () => {
  422. expect(getCreateProjectModeLocation(AlmKeys.GitHub)).toEqual({
  423. search: '?mode=github',
  424. });
  425. });
  426. });