import Password from './Password';
import Tokens from './Tokens';
-interface Props {
+export interface SecurityProps {
user: T.LoggedInUser;
}
-function Security({ user }: Props) {
+export function Security({ user }: SecurityProps) {
return (
<div className="account-body account-container">
<Helmet defer={false} title={translate('my_account.security')} />
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockLoggedInUser } from '../../../../helpers/testMocks';
+import { Security, SecurityProps } from '../Security';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('local user');
+ expect(shallowRender({ user: mockLoggedInUser({ local: false }) })).toMatchSnapshot(
+ 'non-local user'
+ );
+});
+
+function shallowRender(props: Partial<SecurityProps> = {}) {
+ return shallow(<Security user={mockLoggedInUser({ local: true })} {...props} />);
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: local user 1`] = `
+<div
+ className="account-body account-container"
+>
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="my_account.security"
+ />
+ <Tokens
+ login="luke"
+ />
+ <Password
+ user={
+ Object {
+ "groups": Array [],
+ "isLoggedIn": true,
+ "local": true,
+ "login": "luke",
+ "name": "Skywalker",
+ "scmAccounts": Array [],
+ }
+ }
+ />
+</div>
+`;
+
+exports[`should render correctly: non-local user 1`] = `
+<div
+ className="account-body account-container"
+>
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="my_account.security"
+ />
+ <Tokens
+ login="luke"
+ />
+</div>
+`;
import './style.css';
import { filterPlugins, parseQuery, Query, serializeQuery } from './utils';
-export interface Props {
+interface Props {
currentEdition?: EditionKey;
fetchPendingPlugins: () => void;
pendingPlugins: PluginPendingResult;
plugins: Plugin[];
}
-class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loadingPlugins: true, plugins: [] };
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import {
+ getAvailablePlugins,
+ getInstalledPlugins,
+ getInstalledPluginsWithUpdates,
+ getPluginUpdates
+} from '../../../api/plugins';
+import { mockLocation, mockRouter } from '../../../helpers/testMocks';
+import { App } from '../App';
+
+jest.mock('../../../api/plugins', () => ({
+ getAvailablePlugins: jest.fn().mockResolvedValue({ plugins: [] }),
+ getInstalledPlugins: jest.fn().mockResolvedValue([]),
+ getInstalledPluginsWithUpdates: jest.fn().mockResolvedValue([]),
+ getPluginUpdates: jest.fn().mockResolvedValue([])
+}));
+
+beforeEach(jest.clearAllMocks);
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot('loading');
+ expect(wrapper.setState({ loadingPlugins: false })).toMatchSnapshot('loaded');
+});
+
+it('should fetch plugin info', async () => {
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+ expect(getInstalledPluginsWithUpdates).toBeCalled();
+ expect(getAvailablePlugins).toBeCalled();
+
+ wrapper.setProps({ location: mockLocation({ query: { filter: 'updates' } }) });
+ await waitAndUpdate(wrapper);
+ expect(getPluginUpdates).toBeCalled();
+
+ wrapper.setProps({ location: mockLocation({ query: { filter: 'installed' } }) });
+ await waitAndUpdate(wrapper);
+ expect(getInstalledPlugins).toBeCalled();
+});
+
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow<App>(
+ <App
+ fetchPendingPlugins={jest.fn()}
+ location={mockLocation()}
+ pendingPlugins={{
+ installing: [],
+ updating: [],
+ removing: []
+ }}
+ router={mockRouter()}
+ updateCenterActive={false}
+ {...props}
+ />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: loaded 1`] = `
+<div
+ className="page page-limited"
+ id="marketplace-page"
+>
+ <Suggestions
+ suggestions="marketplace"
+ />
+ <Helmet
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="marketplace.page"
+ />
+ <Header />
+ <EditionBoxes />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ marketplace.page.open_source_plugins
+ </h1>
+ </header>
+ <Search
+ query={
+ Object {
+ "filter": "all",
+ "search": "",
+ }
+ }
+ updateCenterActive={false}
+ updateQuery={[Function]}
+ />
+ <PluginsList
+ pending={
+ Object {
+ "installing": Array [],
+ "removing": Array [],
+ "updating": Array [],
+ }
+ }
+ plugins={Array []}
+ readOnly={true}
+ refreshPending={[MockFunction]}
+ />
+ <Footer
+ total={0}
+ />
+</div>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<div
+ className="page page-limited"
+ id="marketplace-page"
+>
+ <Suggestions
+ suggestions="marketplace"
+ />
+ <Helmet
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="marketplace.page"
+ />
+ <Header />
+ <EditionBoxes />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ marketplace.page.open_source_plugins
+ </h1>
+ </header>
+ <Search
+ query={
+ Object {
+ "filter": "all",
+ "search": "",
+ }
+ }
+ updateCenterActive={false}
+ updateQuery={[Function]}
+ />
+ <i
+ className="spinner"
+ />
+</div>
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { checkSecretKey, generateSecretKey } from '../../../../api/settings';
+import EncryptionApp from '../EncryptionApp';
+
+jest.mock('../../../../api/settings', () => ({
+ checkSecretKey: jest.fn().mockResolvedValue({ secretKeyAvailable: true }),
+ generateSecretKey: jest.fn().mockResolvedValue({ secretKey: 'secret' })
+}));
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot('loading');
+ expect(wrapper.setState({ loading: false, secretKeyAvailable: false })).toMatchSnapshot(
+ 'generate form'
+ );
+ expect(wrapper.setState({ secretKeyAvailable: true })).toMatchSnapshot('encryption form');
+});
+
+it('should correctly check a key', async () => {
+ const wrapper = shallowRender();
+ wrapper.instance().checkSecretKey();
+ await waitAndUpdate(wrapper);
+ expect(checkSecretKey).toBeCalled();
+ expect(wrapper.state().secretKeyAvailable).toBe(true);
+});
+
+it('should correctly generate a key', async () => {
+ const wrapper = shallowRender();
+ wrapper.instance().generateSecretKey();
+ await waitAndUpdate(wrapper);
+ expect(generateSecretKey).toBeCalled();
+ expect(wrapper.state().secretKey).toBe('secret');
+ expect(wrapper.state().secretKeyAvailable).toBe(false);
+});
+
+function shallowRender(props: Partial<EncryptionApp['props']> = {}) {
+ return shallow<EncryptionApp>(<EncryptionApp {...props} />);
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: encryption form 1`] = `
+<div
+ className="page page-limited"
+ id="encryption-page"
+>
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="property.category.security.encryption"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ property.category.security.encryption
+ </h1>
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ />
+ </header>
+ <EncryptionForm
+ generateSecretKey={[Function]}
+ />
+</div>
+`;
+
+exports[`should render correctly: generate form 1`] = `
+<div
+ className="page page-limited"
+ id="encryption-page"
+>
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="property.category.security.encryption"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ property.category.security.encryption
+ </h1>
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ />
+ </header>
+ <GenerateSecretKeyForm
+ generateSecretKey={[Function]}
+ />
+</div>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<div
+ className="page page-limited"
+ id="encryption-page"
+>
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="property.category.security.encryption"
+ />
+ <header
+ className="page-header"
+ >
+ <h1
+ className="page-title"
+ >
+ property.category.security.encryption
+ </h1>
+ <DeferredSpinner
+ loading={true}
+ timeout={100}
+ />
+ </header>
+</div>
+`;
sysInfoData?: T.SysInfoCluster | T.SysInfoStandalone;
}
-class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { getSystemInfo } from '../../../../api/system';
+import {
+ mockClusterSysInfo,
+ mockLocation,
+ mockRouter,
+ mockStandaloneSysInfo
+} from '../../../../helpers/testMocks';
+import { App } from '../App';
+
+jest.mock('../../../../api/system', () => ({
+ getSystemInfo: jest.fn().mockResolvedValue(null)
+}));
+
+beforeEach(jest.clearAllMocks);
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot('loading');
+ expect(
+ wrapper.setState({ loading: false, sysInfoData: mockStandaloneSysInfo() })
+ ).toMatchSnapshot('stand-alone sysinfo');
+ expect(wrapper.setState({ loading: false, sysInfoData: mockClusterSysInfo() })).toMatchSnapshot(
+ 'cluster sysinfo'
+ );
+});
+
+it('should fetch system info', async () => {
+ const sysInfoData = mockStandaloneSysInfo();
+ (getSystemInfo as jest.Mock).mockResolvedValue(sysInfoData);
+
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(getSystemInfo).toBeCalled();
+ expect(wrapper.state().sysInfoData).toBe(sysInfoData);
+});
+
+it('should toggle cards and update the URL', () => {
+ const replace = jest.fn();
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ // Open
+ wrapper.instance().toggleSysInfoCards('foo');
+ expect(replace).toBeCalledWith(
+ expect.objectContaining({
+ query: { expand: 'foo' }
+ })
+ );
+
+ // Close.
+ replace.mockClear();
+ wrapper.setProps({ location: mockLocation({ query: { expand: 'foo,bar' } }) });
+ wrapper.instance().toggleSysInfoCards('foo');
+ expect(replace).toBeCalledWith(
+ expect.objectContaining({
+ query: { expand: 'bar' }
+ })
+ );
+});
+
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow<App>(
+ <App location={mockLocation()} params={{}} router={mockRouter()} routes={[]} {...props} />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: cluster sysinfo 1`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="system_info"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="system_info.page"
+ />
+ <SystemUpgradeNotif />
+ <PageHeader
+ isCluster={true}
+ loading={false}
+ logLevel="DEBUG"
+ onLogLevelChange={[Function]}
+ serverId="asd564-asd54a-5dsfg45"
+ showActions={true}
+ version="7.8"
+ />
+ <ClusterSysInfos
+ expandedCards={Array []}
+ sysInfoData={
+ Object {
+ "Application Nodes": Array [
+ Object {
+ "Compute Engine Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Initial Size": 0,
+ },
+ "Compute Engine JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Compute Engine JVM State": Object {
+ "Free Memory (MB)": 78,
+ "Max Memory (MB)": 1024,
+ },
+ "Compute Engine Logging": Object {
+ "Logs Level": "INFO",
+ },
+ "Compute Engine Tasks": Object {
+ "In Progress": 0,
+ "Pending": 0,
+ },
+ "Health": "GREEN",
+ "Health Causes": Array [],
+ "Host": "10.0.0.0",
+ "Name": "server9.example.com",
+ "Plugins": Object {
+ "java": "5.13.0.17924 [SonarJava]",
+ },
+ "System": Object {
+ "Version": "7.8",
+ },
+ "Web Database Connection": Object {
+ "Pool Active Connections": 1,
+ },
+ "Web JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Web JVM State": Object {
+ "Free Memory (MB)": 122,
+ "Max Memory (MB)": 1024,
+ },
+ "Web Logging": Object {
+ "Logs Level": "DEBUG",
+ },
+ },
+ Object {
+ "Compute Engine Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Initial Size": 0,
+ },
+ "Compute Engine JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Compute Engine JVM State": Object {
+ "Free Memory (MB)": 89,
+ "Max Memory (MB)": 1024,
+ },
+ "Compute Engine Logging": Object {
+ "Logs Level": "INFO",
+ },
+ "Compute Engine Tasks": Object {
+ "In Progress": 0,
+ "Pending": 0,
+ },
+ "Health": "GREEN",
+ "Health Causes": Array [],
+ "Host": "10.0.0.0",
+ "Name": "server9.example.com",
+ "Plugins": Object {
+ "java": "5.13.0.17924 [SonarJava]",
+ },
+ "System": Object {
+ "Version": "7.8",
+ },
+ "Web Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Max Connections": 60,
+ },
+ "Web JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Web JVM State": Object {
+ "Free Memory (MB)": 111,
+ "Max Memory (MB)": 1024,
+ },
+ "Web Logging": Object {
+ "Logs Level": "INFO",
+ },
+ },
+ ],
+ "Compute Engine Tasks": Object {
+ "Total In Progress": 0,
+ "Total Pending": 0,
+ },
+ "Database": Object {
+ "Database": "PostgreSQL",
+ "Database Version": "10.3",
+ "Driver": "PostgreSQL JDBC Driver",
+ "Driver Version": "42.2.5",
+ "URL": "jdbc:postgresql://localhost/sonar",
+ "Username": "sonar",
+ },
+ "Health": "GREEN",
+ "Health Causes": Array [],
+ "Search Indexes": Object {
+ "Index components - Docs": 30445,
+ "Index components - Shards": 10,
+ },
+ "Search Nodes": Array [
+ Object {
+ "Host": "10.0.0.0",
+ "Name": "server.example.com",
+ "Search State": Object {
+ "CPU Usage (%)": 0,
+ "Disk Available": "93 GB",
+ },
+ },
+ Object {
+ "Host": "10.0.0.0",
+ "Name": "server.example.com",
+ "Search State": Object {
+ "CPU Usage (%)": 0,
+ "Disk Available": "93 GB",
+ },
+ },
+ Object {
+ "Host": "10.0.0.0",
+ "Name": "server.example.com",
+ "Search State": Object {
+ "CPU Usage (%)": 0,
+ "Disk Available": "93 GB",
+ },
+ },
+ ],
+ "Search State": Object {
+ "Nodes": 3,
+ "State": "GREEN",
+ },
+ "Settings": Object {
+ "sonar.cluster.enabled": "true",
+ "sonar.cluster.node.name": "server9.example.com",
+ },
+ "Statistics": Object {
+ "ncloc": 989880,
+ },
+ "System": Object {
+ "High Availability": true,
+ "Server ID": "asd564-asd54a-5dsfg45",
+ "Version": "7.8",
+ },
+ }
+ }
+ toggleCard={[Function]}
+ />
+</div>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="system_info"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="system_info.page"
+ />
+ <SystemUpgradeNotif />
+</div>
+`;
+
+exports[`should render correctly: stand-alone sysinfo 1`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="system_info"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="system_info.page"
+ />
+ <SystemUpgradeNotif />
+ <PageHeader
+ isCluster={false}
+ loading={false}
+ logLevel="DEBUG"
+ onLogLevelChange={[Function]}
+ serverId="asd564-asd54a-5dsfg45"
+ showActions={true}
+ version="7.8"
+ />
+ <StandAloneSysInfos
+ expandedCards={Array []}
+ sysInfoData={
+ Object {
+ "Compute Engine Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Initial Size": 0,
+ },
+ "Compute Engine JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Compute Engine JVM State": Object {
+ "Free Memory (MB)": 89,
+ "Max Memory (MB)": 1024,
+ },
+ "Compute Engine Logging": Object {
+ "Logs Dir": "/logs",
+ "Logs Level": "DEBUG",
+ },
+ "Compute Engine Tasks": Object {
+ "In Progress": 0,
+ "Pending": 0,
+ },
+ "Database": Object {
+ "Database": "PostgreSQL",
+ "Database Version": "10.3",
+ "Driver": "PostgreSQL JDBC Driver",
+ "Driver Version": "42.2.5",
+ "URL": "jdbc:postgresql://localhost/sonar",
+ "Username": "sonar",
+ },
+ "Health": "GREEN",
+ "Health Causes": Array [],
+ "Search Indexes": Object {
+ "Index components - Docs": 30445,
+ "Index components - Shards": 10,
+ },
+ "Search State": Object {
+ "Nodes": 3,
+ "State": "GREEN",
+ },
+ "Settings": Object {
+ "sonar.cluster.enabled": "true",
+ "sonar.cluster.node.name": "server9.example.com",
+ },
+ "System": Object {
+ "High Availability": false,
+ "Server ID": "asd564-asd54a-5dsfg45",
+ "Version": "7.8",
+ },
+ "Web Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Max Connections": 60,
+ },
+ "Web JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Web JVM State": Object {
+ "Free Memory (MB)": 111,
+ "Max Memory (MB)": 1024,
+ },
+ "Web Logging": Object {
+ "Logs Dir": "/logs",
+ "Logs Level": "INFO",
+ },
+ }
+ }
+ toggleCard={[Function]}
+ />
+</div>
+`;