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.

ComponentsServiceMock.ts 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  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 { cloneDeep, pick } from 'lodash';
  21. import { DEFAULT_METRICS } from '../../helpers/mocks/metrics';
  22. import { HttpStatus, RequestData } from '../../helpers/request';
  23. import { mockMetric } from '../../helpers/testMocks';
  24. import { isDefined } from '../../helpers/types';
  25. import { BranchParameters } from '../../types/branch-like';
  26. import { TreeComponent, Visibility } from '../../types/component';
  27. import {
  28. Component,
  29. ComponentMeasure,
  30. Dict,
  31. DuplicatedFile,
  32. Duplication,
  33. Metric,
  34. Paging,
  35. } from '../../types/types';
  36. import {
  37. ComponentRaw,
  38. GetTreeParams,
  39. changeKey,
  40. doesComponentExists,
  41. getBreadcrumbs,
  42. getChildren,
  43. getComponentData,
  44. getComponentForSourceViewer,
  45. getComponentLeaves,
  46. getComponentTree,
  47. getDuplications,
  48. getSources,
  49. getTree,
  50. searchProjects,
  51. setApplicationTags,
  52. setProjectTags,
  53. } from '../components';
  54. import {
  55. ComponentTree,
  56. SourceFile,
  57. mockFullComponentTree,
  58. mockFullSourceViewerFileList,
  59. } from './data/components';
  60. import { mockIssuesList } from './data/issues';
  61. import { MeasureRecords, mockFullMeasureData } from './data/measures';
  62. import { mockProjects } from './data/projects';
  63. import { listAllComponent, listChildComponent, listLeavesComponent } from './data/utils';
  64. jest.mock('../components');
  65. export default class ComponentsServiceMock {
  66. failLoadingComponentStatus: HttpStatus | undefined = undefined;
  67. defaultComponents: ComponentTree[];
  68. defaultProjects: ComponentRaw[];
  69. components: ComponentTree[];
  70. defaultSourceFiles: SourceFile[];
  71. sourceFiles: SourceFile[];
  72. defaultMeasures: MeasureRecords;
  73. measures: MeasureRecords;
  74. projects: ComponentRaw[];
  75. constructor(components?: ComponentTree[], sourceFiles?: SourceFile[], measures?: MeasureRecords) {
  76. this.defaultComponents = components || [mockFullComponentTree()];
  77. this.defaultSourceFiles = sourceFiles || mockFullSourceViewerFileList();
  78. const issueList = mockIssuesList();
  79. this.defaultMeasures =
  80. measures ||
  81. this.defaultComponents.reduce(
  82. (acc, tree) => ({ ...acc, ...mockFullMeasureData(tree, issueList) }),
  83. {},
  84. );
  85. this.defaultProjects = mockProjects();
  86. this.components = cloneDeep(this.defaultComponents);
  87. this.sourceFiles = cloneDeep(this.defaultSourceFiles);
  88. this.measures = cloneDeep(this.defaultMeasures);
  89. this.projects = cloneDeep(this.defaultProjects);
  90. jest.mocked(getComponentTree).mockImplementation(this.handleGetComponentTree);
  91. jest.mocked(getChildren).mockImplementation(this.handleGetChildren);
  92. jest.mocked(getTree).mockImplementation(this.handleGetTree);
  93. jest.mocked(getComponentData).mockImplementation(this.handleGetComponentData);
  94. jest
  95. .mocked(getComponentForSourceViewer)
  96. .mockImplementation(this.handleGetComponentForSourceViewer);
  97. jest.mocked(getDuplications).mockImplementation(this.handleGetDuplications);
  98. jest.mocked(getSources).mockImplementation(this.handleGetSources);
  99. jest.mocked(changeKey).mockImplementation(this.handleChangeKey);
  100. jest.mocked(getComponentLeaves).mockImplementation(this.handleGetComponentLeaves);
  101. jest.mocked(getBreadcrumbs).mockImplementation(this.handleGetBreadcrumbs);
  102. jest.mocked(setProjectTags).mockImplementation(this.handleSetProjectTags);
  103. jest.mocked(setApplicationTags).mockImplementation(this.handleSetApplicationTags);
  104. jest.mocked(searchProjects).mockImplementation(this.handleSearchProjects);
  105. jest.mocked(doesComponentExists).mockImplementation(this.handleDoesComponentExists);
  106. }
  107. handleSearchProjects: typeof searchProjects = (data) => {
  108. const pageIndex = data.p ?? 1;
  109. const pageSize = data.ps ?? 100;
  110. const components = this.projects
  111. .filter((c) => {
  112. if (data.filter && data.filter.startsWith('query')) {
  113. const query = data.filter.split('query=')[1];
  114. return c.key.includes(query) || c.name.includes(query);
  115. }
  116. })
  117. .map((c) => c);
  118. return this.reply({
  119. components: components.slice((pageIndex - 1) * pageSize, pageIndex * pageSize),
  120. facets: [],
  121. paging: {
  122. pageSize,
  123. pageIndex,
  124. total: components.length,
  125. },
  126. });
  127. };
  128. findComponentTree = (key: string, from?: ComponentTree) => {
  129. let tree: ComponentTree | undefined;
  130. const recurse = (node: ComponentTree): boolean => {
  131. if (node.component.key === key) {
  132. tree = node;
  133. return true;
  134. }
  135. return node.children.some((child) => recurse(child));
  136. };
  137. if (from !== undefined) {
  138. recurse(from);
  139. return tree;
  140. }
  141. for (let i = 0, len = this.components.length; i < len; i++) {
  142. if (recurse(this.components[i])) {
  143. return tree;
  144. }
  145. }
  146. throw new Error(`Couldn't find component tree for key ${key}`);
  147. };
  148. findSourceFile = (key: string): SourceFile => {
  149. const sourceFile = this.sourceFiles.find((s) => s.component.key === key);
  150. if (sourceFile) {
  151. return sourceFile;
  152. }
  153. throw new Error(`Couldn't find source file for key ${key}`);
  154. };
  155. registerComponent = (component: Component, ancestors: Component[] = []) => {
  156. this.components.push({ component, ancestors, children: [] });
  157. };
  158. registerComponentTree = (componentTree: ComponentTree, replace = true) => {
  159. if (replace) {
  160. this.components = [];
  161. }
  162. this.components.push(componentTree);
  163. };
  164. registerComponentMeasures = (measures: MeasureRecords) => {
  165. this.measures = measures;
  166. };
  167. setFailLoadingComponentStatus = (status: HttpStatus.Forbidden | HttpStatus.NotFound) => {
  168. this.failLoadingComponentStatus = status;
  169. };
  170. getHugeFileKey = () => {
  171. const { sourceFile } = this.sourceFiles.reduce(
  172. (acc, sourceFile) => {
  173. if (sourceFile.lines.length > acc.size) {
  174. return {
  175. sourceFile,
  176. size: sourceFile.lines.length,
  177. };
  178. }
  179. return acc;
  180. },
  181. { sourceFile: undefined, size: -Infinity },
  182. );
  183. if (sourceFile) {
  184. return sourceFile.component.key;
  185. }
  186. throw new Error('Could not find a large source file');
  187. };
  188. getEmptyFileKey = () => {
  189. const sourceFile = this.sourceFiles.find((sourceFile) => {
  190. if (sourceFile.lines.length === 0) {
  191. return sourceFile;
  192. }
  193. });
  194. if (sourceFile) {
  195. return sourceFile.component.key;
  196. }
  197. throw new Error('Could not find an empty source file');
  198. };
  199. getNonEmptyFileKey = (preferredKey = 'foo:test1.js') => {
  200. let sourceFile = this.sourceFiles.find((sourceFile) => {
  201. if (sourceFile.component.key === preferredKey) {
  202. return sourceFile;
  203. }
  204. });
  205. if (!sourceFile) {
  206. sourceFile = this.sourceFiles.find((sourceFile) => {
  207. if (sourceFile.lines.length > 0) {
  208. return sourceFile;
  209. }
  210. });
  211. }
  212. if (sourceFile) {
  213. return sourceFile.component.key;
  214. }
  215. throw new Error('Could not find a non-empty source file');
  216. };
  217. reset = () => {
  218. this.components = cloneDeep(this.defaultComponents);
  219. this.sourceFiles = cloneDeep(this.defaultSourceFiles);
  220. this.measures = cloneDeep(this.defaultMeasures);
  221. };
  222. handleGetChildren = (
  223. component: string,
  224. metrics: string[] = [],
  225. data: RequestData = {},
  226. ): Promise<{
  227. baseComponent: ComponentMeasure;
  228. components: ComponentMeasure[];
  229. metrics: Metric[];
  230. paging: Paging;
  231. }> => {
  232. return this.handleGetComponentTree('children', component, metrics, data);
  233. };
  234. handleGetComponentTree = (
  235. strategy: string,
  236. key: string,
  237. metricKeys: string[] = [],
  238. { p = 1, ps = 100 }: RequestData = {},
  239. ): Promise<{
  240. baseComponent: ComponentMeasure;
  241. components: ComponentMeasure[];
  242. metrics: Metric[];
  243. paging: Paging;
  244. }> => {
  245. const base = this.findComponentTree(key);
  246. let components: Component[] = [];
  247. if (base === undefined) {
  248. return Promise.reject({
  249. errors: [{ msg: `No component has been found for id ${key}` }],
  250. });
  251. }
  252. if (strategy === 'all' || strategy === '') {
  253. components = listAllComponent(base);
  254. } else if (strategy === 'children') {
  255. components = listChildComponent(base);
  256. } else if (strategy === 'leaves') {
  257. components = listLeavesComponent(base);
  258. }
  259. const componentsMeasures: ComponentMeasure[] = components.map((c) => {
  260. return {
  261. measures: metricKeys
  262. .map((metric) => this.measures[c.key] && this.measures[c.key][metric])
  263. .filter(isDefined),
  264. ...pick(c, ['analysisDate', 'key', 'name', 'qualifier']),
  265. };
  266. });
  267. return this.reply({
  268. baseComponent: base.component,
  269. components: componentsMeasures.slice(ps * (p - 1), ps * (p - 1) + ps),
  270. metrics: metricKeys.map((metric) => DEFAULT_METRICS[metric] ?? mockMetric({ key: metric })),
  271. paging: {
  272. pageSize: ps,
  273. pageIndex: p,
  274. total: componentsMeasures.length,
  275. },
  276. });
  277. };
  278. handleGetTree = ({
  279. component: key,
  280. q = '',
  281. qualifiers,
  282. }: GetTreeParams & { qualifiers?: string }): Promise<{
  283. baseComponent: TreeComponent;
  284. components: TreeComponent[];
  285. paging: Paging;
  286. }> => {
  287. const base = this.findComponentTree(key);
  288. if (base === undefined) {
  289. return Promise.reject({
  290. errors: [{ msg: `No component has been found for key ${key}` }],
  291. });
  292. }
  293. const components: TreeComponent[] = listAllComponent(base)
  294. .filter(({ name, key }) => name.includes(q) || key.includes(q))
  295. .filter(({ qualifier }) => (qualifiers?.length ? qualifiers.includes(qualifier) : true))
  296. .map((c) => ({ ...c, visibility: Visibility.Public }));
  297. return this.reply({
  298. baseComponent: { ...base.component, visibility: Visibility.Public },
  299. components,
  300. paging: {
  301. pageIndex: 1,
  302. pageSize: 100,
  303. total: components.length,
  304. },
  305. });
  306. };
  307. handleGetComponentData = (data: { component: string } & BranchParameters) => {
  308. if (this.failLoadingComponentStatus !== undefined) {
  309. return Promise.reject({ status: this.failLoadingComponentStatus });
  310. }
  311. const tree = this.findComponentTree(data.component);
  312. if (tree) {
  313. const { component, ancestors } = tree;
  314. return this.reply({ component, ancestors } as {
  315. component: ComponentRaw;
  316. ancestors: ComponentRaw[];
  317. });
  318. }
  319. throw new Error(`Couldn't find component with key ${data.component}`);
  320. };
  321. handleGetComponentForSourceViewer = ({ component }: { component: string } & BranchParameters) => {
  322. const sourceFile = this.findSourceFile(component);
  323. return this.reply(sourceFile.component);
  324. };
  325. handleGetDuplications = ({
  326. key,
  327. }: { key: string } & BranchParameters): Promise<{
  328. duplications: Duplication[];
  329. files: Dict<DuplicatedFile>;
  330. }> => {
  331. const { duplication } = this.findSourceFile(key);
  332. if (duplication) {
  333. return this.reply(duplication);
  334. }
  335. return this.reply({ duplications: [], files: {} });
  336. };
  337. handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
  338. const { lines } = this.findSourceFile(data.key);
  339. const from = data.from || 1;
  340. const to = data.to || lines.length;
  341. return this.reply(lines.slice(from - 1, to));
  342. };
  343. handleChangeKey = (data: { from: string; to: string }) => {
  344. const treeItem = this.components.find(({ component }) => component.key === data.from);
  345. if (treeItem) {
  346. treeItem.component.key = data.to;
  347. return this.reply(undefined);
  348. }
  349. return Promise.reject({ status: 404, message: 'Component not found' });
  350. };
  351. handleGetComponentLeaves = (
  352. component: string,
  353. metrics: string[] = [],
  354. data: RequestData = {},
  355. ): Promise<{
  356. baseComponent: ComponentMeasure;
  357. components: ComponentMeasure[];
  358. metrics: Metric[];
  359. paging: Paging;
  360. }> => {
  361. return this.handleGetComponentTree('leaves', component, metrics, data);
  362. };
  363. handleGetBreadcrumbs = ({ component: key }: { component: string } & BranchParameters) => {
  364. const base = this.findComponentTree(key);
  365. if (base === undefined) {
  366. return Promise.reject({
  367. errors: [{ msg: `No component has been found for id ${key}` }],
  368. });
  369. }
  370. return this.reply([...(base.ancestors as ComponentRaw[]), base.component as ComponentRaw]);
  371. };
  372. handleSetProjectTags: typeof setProjectTags = ({ project, tags }) => {
  373. const base = this.findComponentTree(project);
  374. if (base !== undefined) {
  375. base.component.tags = tags.split(',');
  376. }
  377. return this.reply();
  378. };
  379. handleSetApplicationTags: typeof setApplicationTags = ({ application, tags }) => {
  380. const base = this.findComponentTree(application);
  381. if (base !== undefined) {
  382. base.component.tags = tags.split(',');
  383. }
  384. return this.reply();
  385. };
  386. handleDoesComponentExists: typeof doesComponentExists = ({ component }) => {
  387. const exists = this.components.some(({ component: { key } }) => key === component);
  388. return this.reply(exists);
  389. };
  390. reply<T>(): Promise<void>;
  391. reply<T>(response: T): Promise<T>;
  392. reply<T>(response?: T): Promise<T | void> {
  393. return Promise.resolve(response ? cloneDeep(response) : undefined);
  394. }
  395. }