2024-11-13 14:32:59 +00:00
|
|
|
import React from "react";
|
2025-03-12 18:54:29 +00:00
|
|
|
import { screen, waitFor } from "@testing-library/react";
|
2024-11-13 14:32:59 +00:00
|
|
|
import { createCustomRenderer } from "test/test-utils";
|
|
|
|
|
import createMockQuery from "__mocks__/queryMock";
|
|
|
|
|
import createMockUser from "__mocks__/userMock";
|
2026-01-09 16:37:54 +00:00
|
|
|
import createMockTeam from "__mocks__/teamMock";
|
2024-11-13 14:32:59 +00:00
|
|
|
import createMockConfig from "__mocks__/configMock";
|
2025-03-12 18:54:29 +00:00
|
|
|
import userEvent from "@testing-library/user-event";
|
|
|
|
|
import { http, HttpResponse } from "msw";
|
|
|
|
|
import mockServer from "test/mock-server";
|
2025-04-08 13:31:58 +00:00
|
|
|
import { QueryablePlatform } from "interfaces/platform";
|
2024-11-13 14:32:59 +00:00
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
import SaveNewQueryModal from "./SaveNewQueryModal";
|
2024-11-13 14:32:59 +00:00
|
|
|
|
2025-03-12 18:54:29 +00:00
|
|
|
const baseUrl = (path: string) => {
|
|
|
|
|
return `/api/latest/fleet${path}`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockLabels = [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
name: "Fun",
|
|
|
|
|
description: "Computers that like to have a good time",
|
|
|
|
|
label_type: "regular",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
name: "Fresh",
|
|
|
|
|
description: "Laptops with dirty mouths",
|
|
|
|
|
label_type: "regular",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const labelSummariesHandler = http.get(baseUrl("/labels/summary"), () => {
|
|
|
|
|
return HttpResponse.json({
|
|
|
|
|
labels: mockLabels,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-13 14:32:59 +00:00
|
|
|
const mockQuery = createMockQuery();
|
|
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
describe("SaveNewQueryModal", () => {
|
2024-11-13 14:32:59 +00:00
|
|
|
const defaultProps = {
|
|
|
|
|
queryValue: "SELECT * FROM users",
|
|
|
|
|
apiTeamIdForQuery: 1,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
saveQuery: jest.fn(),
|
2025-06-30 23:00:22 +00:00
|
|
|
toggleSaveNewQueryModal: jest.fn(),
|
2024-11-13 14:32:59 +00:00
|
|
|
backendValidators: {},
|
|
|
|
|
existingQuery: mockQuery,
|
|
|
|
|
queryReportsDisabled: false,
|
2025-04-08 13:31:58 +00:00
|
|
|
platformSelector: {
|
|
|
|
|
getSelectedPlatforms: () => ["linux"] as QueryablePlatform[],
|
|
|
|
|
setSelectedPlatforms: jest.fn(),
|
|
|
|
|
isAnyPlatformSelected: true,
|
|
|
|
|
render: () => <></>,
|
|
|
|
|
},
|
2024-11-13 14:32:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it("renders the modal with initial values and allows editing", async () => {
|
|
|
|
|
const render = createCustomRenderer({
|
2025-03-12 18:54:29 +00:00
|
|
|
withBackendMock: true,
|
2024-11-13 14:32:59 +00:00
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
|
|
|
|
config: createMockConfig(),
|
2025-03-12 18:54:29 +00:00
|
|
|
isPremiumTier: false,
|
2024-11-13 14:32:59 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
const { user } = render(<SaveNewQueryModal {...defaultProps} />);
|
2024-11-13 14:32:59 +00:00
|
|
|
|
|
|
|
|
expect(screen.getByLabelText("Name")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByLabelText("Description")).toBeInTheDocument();
|
2025-05-28 16:40:13 +00:00
|
|
|
expect(screen.getByText("Interval")).toBeInTheDocument();
|
2024-11-13 14:32:59 +00:00
|
|
|
expect(screen.getByText("Observers can run")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText("Automations off")).toBeInTheDocument();
|
2026-03-18 23:05:26 +00:00
|
|
|
expect(screen.getByText("Advanced options")).toBeInTheDocument();
|
2024-11-13 14:32:59 +00:00
|
|
|
|
|
|
|
|
const nameInput = screen.getByLabelText("Name");
|
|
|
|
|
await user.type(nameInput, "Test Query");
|
|
|
|
|
expect(nameInput).toHaveValue("Test Query");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("toggles advanced options", async () => {
|
|
|
|
|
const render = createCustomRenderer({
|
2025-03-12 18:54:29 +00:00
|
|
|
withBackendMock: true,
|
2024-11-13 14:32:59 +00:00
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
|
|
|
|
config: createMockConfig(),
|
2025-03-12 18:54:29 +00:00
|
|
|
isPremiumTier: false,
|
2024-11-13 14:32:59 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
const { user } = render(<SaveNewQueryModal {...defaultProps} />);
|
2024-11-13 14:32:59 +00:00
|
|
|
|
2026-03-18 23:05:26 +00:00
|
|
|
const advancedOptionsButton = screen.getByText("Advanced options");
|
2024-11-13 14:32:59 +00:00
|
|
|
await user.click(advancedOptionsButton);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText("Minimum osquery version")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText("Logging")).toBeInTheDocument();
|
|
|
|
|
|
|
|
|
|
await user.click(advancedOptionsButton);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("displays error when query name is empty", async () => {
|
|
|
|
|
const render = createCustomRenderer({
|
2025-03-12 18:54:29 +00:00
|
|
|
withBackendMock: true,
|
2024-11-13 14:32:59 +00:00
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
|
|
|
|
config: createMockConfig(),
|
2025-03-12 18:54:29 +00:00
|
|
|
isPremiumTier: false,
|
2024-11-13 14:32:59 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
const { user } = render(<SaveNewQueryModal {...defaultProps} />);
|
2024-11-13 14:32:59 +00:00
|
|
|
|
|
|
|
|
await user.click(screen.getByText("Save"));
|
|
|
|
|
|
2026-02-17 21:19:33 +00:00
|
|
|
expect(screen.getByText("Report name must be present")).toBeInTheDocument();
|
2024-11-13 14:32:59 +00:00
|
|
|
});
|
2025-03-12 18:54:29 +00:00
|
|
|
|
|
|
|
|
it("should not show the target selector in the free tier", async () => {
|
|
|
|
|
const render = createCustomRenderer({
|
|
|
|
|
withBackendMock: true,
|
|
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
|
|
|
|
config: createMockConfig(),
|
|
|
|
|
isPremiumTier: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...defaultProps} />);
|
2025-03-12 18:54:29 +00:00
|
|
|
|
|
|
|
|
// Wait for any queries (that should not be happening) to finish.
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
|
|
|
|
|
|
// Check that the target selector is not present.
|
|
|
|
|
expect(screen.queryByText("All hosts")).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-08 13:31:58 +00:00
|
|
|
it("should disable the save button in when no platforms are selected", async () => {
|
|
|
|
|
const render = createCustomRenderer({
|
|
|
|
|
withBackendMock: true,
|
|
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
|
|
|
|
isGlobalObserver: false,
|
|
|
|
|
isGlobalAdmin: true,
|
|
|
|
|
isGlobalMaintainer: false,
|
|
|
|
|
isOnGlobalTeam: true,
|
|
|
|
|
isPremiumTier: false,
|
|
|
|
|
isSandboxMode: false,
|
|
|
|
|
config: createMockConfig(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const props = {
|
|
|
|
|
...defaultProps,
|
|
|
|
|
platformSelector: {
|
|
|
|
|
getSelectedPlatforms: () => [] as QueryablePlatform[],
|
|
|
|
|
setSelectedPlatforms: jest.fn(),
|
|
|
|
|
isAnyPlatformSelected: false,
|
|
|
|
|
render: () => <></>,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...props} />);
|
2025-04-08 13:31:58 +00:00
|
|
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
|
|
|
|
expect(saveButton).toBeDisabled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should send platforms when saving a new query", async () => {
|
|
|
|
|
const saveQuery = jest.fn();
|
|
|
|
|
const props = {
|
|
|
|
|
...defaultProps,
|
|
|
|
|
platformSelector: {
|
|
|
|
|
getSelectedPlatforms: () => ["linux", "macos"] as QueryablePlatform[],
|
|
|
|
|
setSelectedPlatforms: jest.fn(),
|
|
|
|
|
isAnyPlatformSelected: true,
|
|
|
|
|
render: () => <></>,
|
|
|
|
|
},
|
|
|
|
|
saveQuery,
|
|
|
|
|
};
|
|
|
|
|
const render = createCustomRenderer({
|
|
|
|
|
withBackendMock: true,
|
|
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
|
|
|
|
isGlobalObserver: false,
|
|
|
|
|
isGlobalAdmin: true,
|
|
|
|
|
isGlobalMaintainer: false,
|
|
|
|
|
isOnGlobalTeam: true,
|
|
|
|
|
isPremiumTier: false,
|
|
|
|
|
isSandboxMode: false,
|
|
|
|
|
config: createMockConfig(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...props} />);
|
2025-04-08 13:31:58 +00:00
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByLabelText("Name")).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
// Set a name.
|
|
|
|
|
await userEvent.type(screen.getByLabelText("Name"), "A Brand New Query!");
|
|
|
|
|
// Set a label.
|
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
|
|
|
expect(saveQuery.mock.calls[0][0].platform).toEqual("linux,macos");
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-12 18:54:29 +00:00
|
|
|
describe("in premium tier", () => {
|
|
|
|
|
const render = createCustomRenderer({
|
|
|
|
|
withBackendMock: true,
|
|
|
|
|
context: {
|
|
|
|
|
app: {
|
|
|
|
|
currentUser: createMockUser(),
|
2026-01-09 16:37:54 +00:00
|
|
|
currentTeam: createMockTeam(),
|
2025-03-12 18:54:29 +00:00
|
|
|
isGlobalObserver: false,
|
|
|
|
|
isGlobalAdmin: true,
|
|
|
|
|
isGlobalMaintainer: false,
|
|
|
|
|
isOnGlobalTeam: true,
|
|
|
|
|
isPremiumTier: true,
|
|
|
|
|
isSandboxMode: false,
|
|
|
|
|
config: createMockConfig(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mockServer.use(labelSummariesHandler);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should show the target selector in All hosts target mode when the query has no labels", async () => {
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...defaultProps} />);
|
2025-03-12 18:54:29 +00:00
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByLabelText("All hosts")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByLabelText("Custom")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByLabelText("All hosts")).toBeChecked();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should disable the save button in Custom target mode when no labels are selected, and enable it once labels are selected", async () => {
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...defaultProps} />);
|
2025-03-12 18:54:29 +00:00
|
|
|
let allHosts;
|
|
|
|
|
let custom;
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
allHosts = screen.getByLabelText("All hosts");
|
|
|
|
|
custom = screen.getByLabelText("Custom");
|
|
|
|
|
expect(allHosts).toBeInTheDocument();
|
|
|
|
|
expect(custom).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
custom && (await userEvent.click(custom));
|
|
|
|
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
|
|
|
|
expect(saveButton).toBeDisabled();
|
|
|
|
|
|
|
|
|
|
const funButton = screen.getByLabelText("Fun");
|
|
|
|
|
expect(funButton).not.toBeChecked();
|
|
|
|
|
await userEvent.click(funButton);
|
|
|
|
|
expect(saveButton).toBeEnabled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should send labels when saving a new query in Custom target mode", async () => {
|
|
|
|
|
const saveQuery = jest.fn();
|
|
|
|
|
const props = { ...defaultProps, saveQuery };
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...props} />);
|
2025-03-12 18:54:29 +00:00
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByLabelText("All hosts")).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Set a name.
|
|
|
|
|
await userEvent.type(screen.getByLabelText("Name"), "A Brand New Query!");
|
|
|
|
|
|
|
|
|
|
// Set a label.
|
|
|
|
|
await userEvent.click(screen.getByLabelText("Custom"));
|
|
|
|
|
await userEvent.click(screen.getByLabelText("Fun"));
|
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
|
|
|
|
|
|
|
|
expect(saveQuery.mock.calls[0][0].labels_include_any).toEqual(["Fun"]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should clear labels when saving a new query in All hosts target mode", async () => {
|
|
|
|
|
const saveQuery = jest.fn();
|
|
|
|
|
const props = { ...defaultProps, saveQuery };
|
2025-06-30 23:00:22 +00:00
|
|
|
render(<SaveNewQueryModal {...props} />);
|
2025-03-12 18:54:29 +00:00
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByLabelText("All hosts")).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Set a name.
|
|
|
|
|
await userEvent.type(screen.getByLabelText("Name"), "A Brand New Query!");
|
|
|
|
|
|
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: "Save" }));
|
|
|
|
|
|
|
|
|
|
expect(saveQuery.mock.calls[0][0].labels_include_any).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-11-13 14:32:59 +00:00
|
|
|
});
|