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.

UsersServiceMock.ts 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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 { isAfter, isBefore } from 'date-fns';
  21. import { cloneDeep, isEmpty, isUndefined, omitBy } from 'lodash';
  22. import { mockClusterSysInfo, mockIdentityProvider, mockRestUser } from '../../helpers/testMocks';
  23. import { IdentityProvider, SysInfoCluster } from '../../types/types';
  24. import { ChangePasswordResults, RestUserDetailed, User } from '../../types/users';
  25. import { getSystemInfo } from '../system';
  26. import { addUserToGroup, removeUserFromGroup } from '../user_groups';
  27. import {
  28. UserGroup,
  29. changePassword,
  30. deleteUser,
  31. getIdentityProviders,
  32. getUserGroups,
  33. getUsers,
  34. postUser,
  35. updateUser,
  36. } from '../users';
  37. jest.mock('../users');
  38. jest.mock('../user_groups');
  39. jest.mock('../system');
  40. const DEFAULT_USERS = [
  41. mockRestUser({
  42. managed: true,
  43. login: 'bob.marley',
  44. name: 'Bob Marley',
  45. sonarQubeLastConnectionDate: '2023-06-27T17:08:59+0200',
  46. sonarLintLastConnectionDate: '2023-06-27T17:08:59+0200',
  47. }),
  48. mockRestUser({
  49. managed: false,
  50. login: 'alice.merveille',
  51. name: 'Alice Merveille',
  52. sonarQubeLastConnectionDate: '2023-06-27T17:08:59+0200',
  53. sonarLintLastConnectionDate: '2023-05-27T17:08:59+0200',
  54. email: 'alice.merveille@wonderland.com',
  55. groupsCount: 2,
  56. }),
  57. mockRestUser({
  58. managed: false,
  59. local: false,
  60. login: 'charlie.cox',
  61. name: 'Charlie Cox',
  62. sonarQubeLastConnectionDate: '2023-06-25T17:08:59+0200',
  63. sonarLintLastConnectionDate: '2023-06-20T12:10:59+0200',
  64. externalProvider: 'test',
  65. externalLogin: 'ExternalTest',
  66. }),
  67. mockRestUser({
  68. managed: true,
  69. local: false,
  70. externalProvider: 'test2',
  71. externalLogin: 'UnknownExternalProvider',
  72. login: 'denis.villeneuve',
  73. name: 'Denis Villeneuve',
  74. sonarQubeLastConnectionDate: '2023-06-20T15:08:59+0200',
  75. sonarLintLastConnectionDate: '2023-05-25T10:08:59+0200',
  76. }),
  77. mockRestUser({
  78. managed: true,
  79. login: 'eva.green',
  80. name: 'Eva Green',
  81. sonarQubeLastConnectionDate: '2023-05-27T17:08:59+0200',
  82. }),
  83. mockRestUser({
  84. managed: false,
  85. login: 'franck.grillo',
  86. name: 'Franck Grillo',
  87. }),
  88. ];
  89. const DEFAULT_GROUPS: UserGroup[] = [
  90. {
  91. id: 1001,
  92. name: 'test1',
  93. description: 'test1',
  94. selected: true,
  95. default: true,
  96. },
  97. {
  98. id: 1002,
  99. name: 'test2',
  100. description: 'test2',
  101. selected: true,
  102. default: false,
  103. },
  104. {
  105. id: 1003,
  106. name: 'test3',
  107. description: 'test3',
  108. selected: false,
  109. default: false,
  110. },
  111. ];
  112. const DEFAULT_PASSWORD = 'test';
  113. export default class UsersServiceMock {
  114. isManaged = true;
  115. users = cloneDeep(DEFAULT_USERS);
  116. groups = cloneDeep(DEFAULT_GROUPS);
  117. password = DEFAULT_PASSWORD;
  118. constructor() {
  119. jest.mocked(getSystemInfo).mockImplementation(this.handleGetSystemInfo);
  120. jest.mocked(getIdentityProviders).mockImplementation(this.handleGetIdentityProviders);
  121. jest.mocked(getUsers).mockImplementation((p) => this.handleGetUsers(p));
  122. jest.mocked(postUser).mockImplementation(this.handlePostUser);
  123. jest.mocked(updateUser).mockImplementation(this.handleUpdateUser);
  124. jest.mocked(getUserGroups).mockImplementation(this.handleGetUserGroups);
  125. jest.mocked(addUserToGroup).mockImplementation(this.handleAddUserToGroup);
  126. jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
  127. jest.mocked(changePassword).mockImplementation(this.handleChangePassword);
  128. jest.mocked(deleteUser).mockImplementation(this.handleDeactivateUser);
  129. }
  130. setIsManaged(managed: boolean) {
  131. this.isManaged = managed;
  132. }
  133. getFilteredRestUsers = (filterParams: {
  134. q: string;
  135. managed?: boolean;
  136. sonarQubeLastConnectionDateFrom?: string;
  137. sonarQubeLastConnectionDateTo?: string;
  138. sonarLintLastConnectionDateFrom?: string;
  139. sonarLintLastConnectionDateTo?: string;
  140. }) => {
  141. const {
  142. managed,
  143. q,
  144. sonarQubeLastConnectionDateFrom,
  145. sonarQubeLastConnectionDateTo,
  146. sonarLintLastConnectionDateFrom,
  147. sonarLintLastConnectionDateTo,
  148. } = filterParams;
  149. return this.users.filter((user) => {
  150. if (this.isManaged && managed !== undefined && user.managed !== managed) {
  151. return false;
  152. }
  153. if (q && (!user.login.includes(q) || (user.name && !user.name.includes(q)))) {
  154. return false;
  155. }
  156. if (
  157. sonarQubeLastConnectionDateFrom &&
  158. (user.sonarQubeLastConnectionDate === null ||
  159. isBefore(
  160. new Date(user.sonarQubeLastConnectionDate),
  161. new Date(sonarQubeLastConnectionDateFrom)
  162. ))
  163. ) {
  164. return false;
  165. }
  166. if (
  167. sonarQubeLastConnectionDateTo &&
  168. user.sonarQubeLastConnectionDate &&
  169. isAfter(new Date(user.sonarQubeLastConnectionDate), new Date(sonarQubeLastConnectionDateTo))
  170. ) {
  171. return false;
  172. }
  173. if (
  174. sonarLintLastConnectionDateFrom &&
  175. (user.sonarLintLastConnectionDate === null ||
  176. isBefore(
  177. new Date(user.sonarLintLastConnectionDate),
  178. new Date(sonarLintLastConnectionDateFrom)
  179. ))
  180. ) {
  181. return false;
  182. }
  183. if (
  184. sonarLintLastConnectionDateTo &&
  185. user.sonarLintLastConnectionDate &&
  186. isAfter(new Date(user.sonarLintLastConnectionDate), new Date(sonarLintLastConnectionDateTo))
  187. ) {
  188. return false;
  189. }
  190. return true;
  191. });
  192. };
  193. handleGetUsers: typeof getUsers<RestUserDetailed> = (data) => {
  194. let page = {
  195. pageIndex: 1,
  196. pageSize: 0,
  197. total: 10,
  198. };
  199. if (data.pageIndex !== undefined && data.pageIndex !== page.pageIndex) {
  200. page = { pageIndex: 2, pageSize: 2, total: 10 };
  201. const users = [
  202. mockRestUser({
  203. name: `Local User ${this.users.length + 4}`,
  204. login: `local-user-${this.users.length + 4}`,
  205. }),
  206. mockRestUser({
  207. name: `Local User ${this.users.length + 5}`,
  208. login: `local-user-${this.users.length + 5}`,
  209. }),
  210. ];
  211. return this.reply({ page, users });
  212. }
  213. const users = this.getFilteredRestUsers({
  214. managed: data.managed,
  215. q: data.q,
  216. sonarQubeLastConnectionDateFrom: data.sonarQubeLastConnectionDateFrom,
  217. sonarQubeLastConnectionDateTo: data.sonarQubeLastConnectionDateTo,
  218. sonarLintLastConnectionDateFrom: data.sonarLintLastConnectionDateFrom,
  219. sonarLintLastConnectionDateTo: data.sonarLintLastConnectionDateTo,
  220. });
  221. return this.reply({
  222. page: {
  223. pageIndex: 1,
  224. pageSize: users.length,
  225. total: 10,
  226. },
  227. users,
  228. });
  229. };
  230. handlePostUser = (data: {
  231. email?: string;
  232. local?: boolean;
  233. login: string;
  234. name: string;
  235. password?: string;
  236. scmAccounts: string[];
  237. }) => {
  238. const { email, local, login, name, scmAccounts } = data;
  239. if (scmAccounts.some((a) => isEmpty(a.trim()))) {
  240. return Promise.reject({
  241. status: 400,
  242. json: () => Promise.resolve({ message: 'Error: Empty SCM' }),
  243. });
  244. }
  245. const newUser = mockRestUser({
  246. email,
  247. local,
  248. login,
  249. name,
  250. scmAccounts,
  251. });
  252. this.users.push(newUser);
  253. return this.reply(undefined);
  254. };
  255. handleUpdateUser = (data: {
  256. email?: string;
  257. login: string;
  258. name: string;
  259. scmAccount: string[];
  260. }) => {
  261. const { email, login, name, scmAccount } = data;
  262. const user = this.users.find((u) => u.login === login) as User;
  263. if (!user) {
  264. return Promise.reject('No such user');
  265. }
  266. Object.assign(user, {
  267. ...omitBy({ name, email, scmAccounts: scmAccount }, isUndefined),
  268. });
  269. return this.reply({ user });
  270. };
  271. handleGetIdentityProviders = (): Promise<{ identityProviders: IdentityProvider[] }> => {
  272. return this.reply({
  273. identityProviders: [mockIdentityProvider({ key: 'test' })],
  274. });
  275. };
  276. handleGetSystemInfo = (): Promise<SysInfoCluster> => {
  277. return this.reply(
  278. mockClusterSysInfo(
  279. this.isManaged
  280. ? {
  281. System: {
  282. 'High Availability': true,
  283. 'Server ID': 'asd564-asd54a-5dsfg45',
  284. 'External Users and Groups Provisioning': 'GitHub',
  285. },
  286. }
  287. : {}
  288. )
  289. );
  290. };
  291. handleGetUserGroups: typeof getUserGroups = (data) => {
  292. const filteredGroups = this.groups
  293. .filter((g) => g.name.includes(data.q ?? ''))
  294. .filter((g) => {
  295. switch (data.selected) {
  296. case 'selected':
  297. return g.selected;
  298. case 'deselected':
  299. return !g.selected;
  300. default:
  301. return true;
  302. }
  303. });
  304. return this.reply({
  305. paging: { pageIndex: 1, pageSize: 10, total: filteredGroups.length },
  306. groups: filteredGroups,
  307. });
  308. };
  309. handleAddUserToGroup: typeof addUserToGroup = ({ name }) => {
  310. this.groups = this.groups.map((g) => (g.name === name ? { ...g, selected: true } : g));
  311. this.users.find((u) => u.login === 'alice.merveille')!.groupsCount++;
  312. return this.reply({});
  313. };
  314. handleRemoveUserFromGroup: typeof removeUserFromGroup = ({ name }) => {
  315. let isDefault = false;
  316. this.groups = this.groups.map((g) => {
  317. if (g.name === name) {
  318. if (g.default) {
  319. isDefault = true;
  320. return g;
  321. }
  322. return { ...g, selected: false };
  323. }
  324. return g;
  325. });
  326. this.users.find((u) => u.login === 'alice.merveille')!.groupsCount--;
  327. return isDefault
  328. ? Promise.reject({
  329. errors: [{ msg: 'Cannot remove Default group' }],
  330. })
  331. : this.reply({});
  332. };
  333. handleChangePassword: typeof changePassword = (data) => {
  334. if (data.previousPassword !== this.password) {
  335. return Promise.reject(ChangePasswordResults.OldPasswordIncorrect);
  336. }
  337. if (data.password === this.password) {
  338. return Promise.reject(ChangePasswordResults.NewPasswordSameAsOld);
  339. }
  340. this.password = data.password;
  341. return this.reply({});
  342. };
  343. handleDeactivateUser: typeof deleteUser = (data) => {
  344. const index = this.users.findIndex((u) => u.login === data.login);
  345. const user = this.users.splice(index, 1)[0];
  346. user.active = false;
  347. return this.reply(undefined);
  348. };
  349. reset = () => {
  350. this.isManaged = true;
  351. this.users = cloneDeep(DEFAULT_USERS);
  352. this.groups = cloneDeep(DEFAULT_GROUPS);
  353. this.password = DEFAULT_PASSWORD;
  354. };
  355. reply<T>(response: T): Promise<T> {
  356. return Promise.resolve(cloneDeep(response));
  357. }
  358. }