diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts new file mode 100644 index 0000000000..2ebbd145c9 --- /dev/null +++ b/frontend/__mocks__/configMock.ts @@ -0,0 +1,163 @@ +import { IConfig } from "interfaces/config"; + +const DEFAULT_CONFIG_MOCK: IConfig = { + org_info: { + org_name: "fleet", + org_logo_url: "", + contact_url: "https://fleetdm.com/company/contact", + }, + server_settings: { + server_url: "https://localhost:8080", + live_query_disabled: false, + enable_analytics: true, + deferred_save_host: false, + }, + smtp_settings: { + enable_smtp: false, + configured: false, + sender_address: "", + server: "", + port: 587, + authentication_type: "authtype_username_password", + user_name: "", + password: "********", + enable_ssl_tls: true, + authentication_method: "authmethod_plain", + domain: "", + verify_ssl_certs: true, + enable_start_tls: true, + }, + sso_settings: { + entity_id: "", + issuer_uri: "", + metadata: "", + metadata_url: "", + idp_name: "", + idp_image_url: "", + enable_sso: false, + enable_sso_idp_login: false, + enable_jit_provisioning: false, + enable_jit_role_sync: false, + }, + host_expiry_settings: { + host_expiry_enabled: false, + host_expiry_window: 0, + }, + agent_options: "", + license: { + tier: "free", + expiration: "0001-01-01T00:00:00Z", + device_count: 4, + note: "", + organization: "", + }, + webhook_settings: { + host_status_webhook: { + enable_host_status_webhook: true, + destination_url: "https://server.com", + host_percentage: 5, + days_count: 7, + }, + failing_policies_webhook: { + enable_failing_policies_webhook: true, + destination_url: "https://server.com", + policy_ids: [1, 2, 3], + host_batch_size: 1000, + }, + vulnerabilities_webhook: { + enable_vulnerabilities_webhook: true, + destination_url: "https://server.com", + host_batch_size: 1000, + }, + }, + integrations: { + jira: [], + zendesk: [], + }, + logging: { + debug: false, + json: false, + result: { + plugin: "filesystem", + config: { + status_log_file: + "/var/folders/xh/bxm1d2615tv3vrg4zrxq540h0000gn/T/osquery_status", + result_log_file: + "/var/folders/xh/bxm1d2615tv3vrg4zrxq540h0000gn/T/osquery_result", + enable_log_rotation: false, + enable_log_compression: false, + }, + }, + status: { + plugin: "filesystem", + config: { + status_log_file: + "/var/folders/xh/bxm1d2615tv3vrg4zrxq540h0000gn/T/osquery_status", + result_log_file: + "/var/folders/xh/bxm1d2615tv3vrg4zrxq540h0000gn/T/osquery_result", + enable_log_rotation: false, + enable_log_compression: false, + }, + }, + audit: { + plugin: "", + config: null, + }, + }, + update_interval: { + osquery_detail: 3600000000000, + osquery_policy: 3600000000000, + }, + vulnerabilities: { + cpe_database_url: "", + current_instance_checks: "auto", + cve_feed_prefix_url: "", + databases_path: "", + disable_data_sync: false, + periodicity: 3600000000000, + recent_vulnerability_max_age: 2592000000000000, + }, + sandbox_enabled: false, + features: { + enable_host_users: true, + enable_software_inventory: true, + }, + fleet_desktop: { transparency_url: "https://fleetdm.com/transparency" }, + mdm: { + apple_bm_default_team: "Apples", + apple_bm_enabled_and_configured: true, + apple_bm_terms_expired: false, + enabled_and_configured: true, + macos_updates: { + minimum_version: "", + deadline: "", + }, + macos_settings: { + custom_settings: null, + enable_disk_encryption: false, + }, + macos_setup: { + bootstrap_package: "", + enable_end_user_authentication: false, + macos_setup_assistant: null, + }, + macos_migration: { + enable: false, + mode: "", + webhook_url: "", + }, + end_user_authentication: { + entity_id: "", + issuer_uri: "", + metadata: "", + metadata_url: "", + idp_name: "", + }, + }, +}; + +const createMockConfig = (overrides?: Partial): IConfig => { + return { ...DEFAULT_CONFIG_MOCK, ...overrides }; +}; + +export default createMockConfig; diff --git a/frontend/components/AddHostsModal/AddHostsModal.tests.tsx b/frontend/components/AddHostsModal/AddHostsModal.tests.tsx new file mode 100644 index 0000000000..403ebeb1b9 --- /dev/null +++ b/frontend/components/AddHostsModal/AddHostsModal.tests.tsx @@ -0,0 +1,176 @@ +import React from "react"; +import { screen } from "@testing-library/react"; +import { noop } from "lodash"; +import { createCustomRenderer } from "test/test-utils"; +import createMockConfig from "__mocks__/configMock"; + +import AddHostsModal from "./AddHostsModal"; + +const ENROLL_SECRET = "abcdefg12345678"; + +describe("AddHostsModal", () => { + it("renders loading state", async () => { + const render = createCustomRenderer({ + withBackendMock: true, + context: { + app: { + isPreviewMode: false, + config: createMockConfig(), + }, + }, + }); + + render( + + ); + const loadingSpinner = screen.getByTestId("spinner"); + expect(loadingSpinner).toBeVisible(); + }); + + it("renders platform tabs", async () => { + const render = createCustomRenderer({ + withBackendMock: true, + context: { + app: { + isPreviewMode: false, + config: createMockConfig(), + }, + }, + }); + + const { user } = render( + + ); + + await user.click(screen.getByRole("tab", { name: "macOS" })); + const macOSText = screen.getByText(/--type=pkg/i); + expect(macOSText).toBeInTheDocument(); + + await user.click(screen.getByRole("tab", { name: "Windows" })); + const windowsText = screen.getByText(/--type=msi/i); + expect(windowsText).toBeInTheDocument(); + + await user.click(screen.getByRole("tab", { name: "Linux (RPM)" })); + const linuxRPMText = screen.getByText(/--type=rpm/i); + expect(linuxRPMText).toBeInTheDocument(); + + await user.click(screen.getByRole("tab", { name: "Linux (deb)" })); + const linuxDebText = screen.getByText(/--type=deb/i); + expect(linuxDebText).toBeInTheDocument(); + + await user.click(screen.getByRole("tab", { name: "ChromeOS" })); + const extensionId = screen.getByDisplayValue( + /fleeedmmihkfkeemmipgmhhjemlljidg/i + ); + expect(extensionId).toBeInTheDocument(); + + await user.click(screen.getByRole("tab", { name: "Advanced" })); + const advancedText = screen.getByText(/--type=YOUR_TYPE/i); + expect(advancedText).toBeInTheDocument(); + + await user.click(screen.getByText(/Plain osquery/i)); + const downloadEnrollSecret = screen.getByText( + /Download your enroll secret/i + ); + expect(downloadEnrollSecret).toBeInTheDocument(); + const osquerydCommand = screen.getByDisplayValue( + /osqueryd --flagfile=flagfile.txt --verbose/i + ); + expect(osquerydCommand).toBeInTheDocument(); + }); + + it("renders installer with secret", async () => { + const render = createCustomRenderer({ + withBackendMock: true, + context: { + app: { + isPreviewMode: false, + config: createMockConfig(), + }, + }, + }); + + render( + + ); + + const regex = new RegExp(`${ENROLL_SECRET}`); + const text = screen.getByDisplayValue(regex); + + expect(text).toBeInTheDocument(); + }); + it("renders no enroll secret cta", async () => { + const render = createCustomRenderer({ + withBackendMock: true, + context: { + app: { + isPreviewMode: false, + config: createMockConfig(), + }, + }, + }); + + render( + + ); + + const text = screen.getByText("Something's gone wrong."); + const ctaButton = screen.getByRole("button", { + name: "Manage enroll secrets", + }); + + expect(text).toBeInTheDocument(); + expect(ctaButton).toBeEnabled(); + }); + + it("sandbox mode renders and download disabled until a platform is selected", async () => { + const render = createCustomRenderer({ + withBackendMock: true, + context: { + app: { + isPreviewMode: false, + config: createMockConfig(), + }, + }, + }); + + const { user } = render( + + ); + + const text = screen.getByText("Which platform is your host running?"); + const windowsText = screen.getByText("Windows"); + const downloadButton = screen.getByRole("button", { + name: /Download installer/i, + }); + + expect(text).toBeInTheDocument(); + expect(downloadButton).not.toBeEnabled(); + + await user.click(windowsText); + + expect(downloadButton).toBeEnabled(); + }); +}); diff --git a/frontend/components/AddHostsModal/AddHostsModal.tsx b/frontend/components/AddHostsModal/AddHostsModal.tsx index c955fac977..9f756b69b3 100644 --- a/frontend/components/AddHostsModal/AddHostsModal.tsx +++ b/frontend/components/AddHostsModal/AddHostsModal.tsx @@ -1,4 +1,7 @@ -import React from "react"; +import React, { useContext } from "react"; +import { useQuery } from "react-query"; +import configAPI from "services/entities/config"; +import { AppContext } from "context/app"; import Button from "components/buttons/Button"; import DataError from "components/DataError"; @@ -29,8 +32,22 @@ const AddHostsModal = ({ onCancel, openEnrollSecretModal, }: IAddHostsModal): JSX.Element => { + const { isPreviewMode, config } = useContext(AppContext); const teamDisplayName = (isAnyTeamSelected && currentTeamName) || "Fleet"; + const { + data: certificate, + error: fetchCertificateError, + isFetching: isFetchingCertificate, + } = useQuery( + ["certificate"], + () => configAPI.loadCertificate(), + { + enabled: !isPreviewMode, + refetchOnWindowFocus: false, + } + ); + const onManageEnrollSecretsClick = () => { onCancel(); openEnrollSecretModal && openEnrollSecretModal(); @@ -42,7 +59,6 @@ const AddHostsModal = ({ // See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407. const shouldRenderDownloadInstallersContent = isSandboxMode && !isAnyTeamSelected; - const renderModalContent = () => { if (isLoading) { return ; @@ -68,7 +84,14 @@ const AddHostsModal = ({ return shouldRenderDownloadInstallersContent ? ( ) : ( - + ); }; diff --git a/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx b/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx index 20461af1ef..8466ced1bd 100644 --- a/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx +++ b/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx @@ -103,8 +103,9 @@ const DownloadForm: FunctionComponent = ({ className={`${baseClass}__button--download`} disabled={!selectedInstaller} type="submit" + isLoading={isCheckingForInstaller} > - {isCheckingForInstaller ? : "Download installer"} + Download installer )} diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx index cea11cb8c5..c0b5fc7a59 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx +++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx @@ -1,14 +1,11 @@ import React, { useContext, useState } from "react"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; -import { useQuery } from "react-query"; import FileSaver from "file-saver"; import { NotificationContext } from "context/notification"; -import { AppContext } from "context/app"; // @ts-ignore import { stringToClipboard } from "utilities/copy_text"; - -import configAPI from "services/entities/config"; +import { IConfig } from "interfaces/config"; import Button from "components/buttons/Button"; import Icon from "components/Icon/Icon"; @@ -58,6 +55,10 @@ const platformSubNav: IPlatformSubNav[] = [ interface IPlatformWrapperProps { enrollSecret: string; onCancel: () => void; + certificate: any; + isFetchingCertificate: boolean; + fetchCertificateError: any; + config: IConfig | null; } const CHROME_OS_INFO = { @@ -78,8 +79,11 @@ const baseClass = "platform-wrapper"; const PlatformWrapper = ({ enrollSecret, onCancel, + certificate, + isFetchingCertificate, + fetchCertificateError, + config, }: IPlatformWrapperProps): JSX.Element => { - const { config, isPreviewMode } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); const [copyMessage, setCopyMessage] = useState>({}); @@ -87,19 +91,6 @@ const PlatformWrapper = ({ const [showPlainOsquery, setShowPlainOsquery] = useState(false); const [selectedTabIndex, setSelectedTabIndex] = useState(0); // External link requires control in state - const { - data: certificate, - error: fetchCertificateError, - isFetching: isFetchingCertificate, - } = useQuery( - ["certificate"], - () => configAPI.loadCertificate(), - { - enabled: !isPreviewMode, - refetchOnWindowFocus: false, - } - ); - let tlsHostname = config?.server_settings.server_url || ""; try { diff --git a/frontend/components/Spinner/Spinner.tsx b/frontend/components/Spinner/Spinner.tsx index a2acb9d168..ceaa217740 100644 --- a/frontend/components/Spinner/Spinner.tsx +++ b/frontend/components/Spinner/Spinner.tsx @@ -33,7 +33,7 @@ const Spinner = ({ "include-container": includeContainer, }); return ( -
+