Files
outline/server/models/helpers/SearchHelper.test.ts
Apoorv Mishra 1490c3a14b Individual document sharing with permissions (#5814)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2024-01-30 17:48:22 -08:00

507 lines
15 KiB
TypeScript

import { DocumentPermission } from "@shared/types";
import SearchHelper from "@server/models/helpers/SearchHelper";
import {
buildDocument,
buildDraftDocument,
buildCollection,
buildTeam,
buildUser,
buildShare,
} from "@server/test/factories";
import UserMembership from "../UserMembership";
beforeEach(async () => {
jest.resetAllMocks();
await buildDocument();
});
describe("SearchHelper", () => {
describe("#searchForTeam", () => {
test("should return search results from public collections", async () => {
const team = await buildTeam();
const collection = await buildCollection({
teamId: team.id,
});
const document = await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test",
});
const { results } = await SearchHelper.searchForTeam(team, "test");
expect(results.length).toBe(1);
expect(results[0].document?.id).toBe(document.id);
});
test("should not return results from private collections without providing collectionId", async () => {
const team = await buildTeam();
const collection = await buildCollection({
permission: null,
teamId: team.id,
});
await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test",
});
const { results } = await SearchHelper.searchForTeam(team, "test");
expect(results.length).toBe(0);
});
test("should return results from private collections when collectionId is provided", async () => {
const team = await buildTeam();
const collection = await buildCollection({
permission: null,
teamId: team.id,
});
await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test",
});
const { results } = await SearchHelper.searchForTeam(team, "test", {
collectionId: collection.id,
});
expect(results.length).toBe(1);
});
test("should return results from document tree of shared document", async () => {
const team = await buildTeam();
const collection = await buildCollection({
permission: null,
teamId: team.id,
});
const document = await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test 1",
});
await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test 2",
});
const share = await buildShare({
documentId: document.id,
includeChildDocuments: true,
});
const { results } = await SearchHelper.searchForTeam(team, "test", {
collectionId: collection.id,
share,
});
expect(results.length).toBe(1);
});
test("should handle no collections", async () => {
const team = await buildTeam();
const { results } = await SearchHelper.searchForTeam(team, "test");
expect(results.length).toBe(0);
});
test("should handle backslashes in search term", async () => {
const team = await buildTeam();
const { results } = await SearchHelper.searchForTeam(team, "\\\\");
expect(results.length).toBe(0);
});
test("should return the total count of search results", async () => {
const team = await buildTeam();
const collection = await buildCollection({
teamId: team.id,
});
await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test number 1",
});
await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test number 2",
});
const { totalCount } = await SearchHelper.searchForTeam(team, "test");
expect(totalCount).toBe(2);
});
test("should return the document when searched with their previous titles", async () => {
const team = await buildTeam();
const collection = await buildCollection({
teamId: team.id,
});
const document = await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test number 1",
});
document.title = "change";
await document.save();
const { totalCount } = await SearchHelper.searchForTeam(
team,
"test number"
);
expect(totalCount).toBe(1);
});
test("should not return the document when searched with neither the titles nor the previous titles", async () => {
const team = await buildTeam();
const collection = await buildCollection({
teamId: team.id,
});
const document = await buildDocument({
teamId: team.id,
collectionId: collection.id,
title: "test number 1",
});
document.title = "change";
await document.save();
const { totalCount } = await SearchHelper.searchForTeam(
team,
"title doesn't exist"
);
expect(totalCount).toBe(0);
});
});
describe("#searchForUser", () => {
test("should return search results from collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
userId: user.id,
teamId: team.id,
});
const document = await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection.id,
title: "test",
});
await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection.id,
deletedAt: new Date(),
title: "test",
});
const { results } = await SearchHelper.searchForUser(user, "test");
expect(results.length).toBe(1);
expect(results[0].document?.id).toBe(document.id);
});
test("should handle no collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const { results } = await SearchHelper.searchForUser(user, "test");
expect(results.length).toBe(0);
});
test("should search only drafts created by user", async () => {
const user = await buildUser();
await buildDraftDocument({
title: "test",
});
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
const { results } = await SearchHelper.searchForUser(user, "test", {
includeDrafts: true,
});
expect(results.length).toBe(1);
});
test("should not include drafts", async () => {
const user = await buildUser();
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
const { results } = await SearchHelper.searchForUser(user, "test", {
includeDrafts: false,
});
expect(results.length).toBe(0);
});
test("should not include drafts with user permission", async () => {
const user = await buildUser();
const draft = await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
await UserMembership.create({
createdById: user.id,
documentId: draft.id,
userId: user.id,
permission: DocumentPermission.Read,
});
const { results } = await SearchHelper.searchForUser(user, "test", {
includeDrafts: false,
});
expect(results.length).toBe(0);
});
test("should include results from drafts as well", async () => {
const user = await buildUser();
await buildDocument({
userId: user.id,
teamId: user.teamId,
createdById: user.id,
title: "not draft",
});
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "draft",
});
const { results } = await SearchHelper.searchForUser(user, "draft", {
includeDrafts: true,
});
expect(results.length).toBe(2);
});
test("should not include results from drafts", async () => {
const user = await buildUser();
await buildDocument({
userId: user.id,
teamId: user.teamId,
createdById: user.id,
title: "not draft",
});
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "draft",
});
const { results } = await SearchHelper.searchForUser(user, "draft", {
includeDrafts: false,
});
expect(results.length).toBe(1);
});
test("should return the total count of search results", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
userId: user.id,
teamId: team.id,
});
await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection.id,
title: "test number 1",
});
await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection.id,
title: "test number 2",
});
const { totalCount } = await SearchHelper.searchForUser(user, "test");
expect(totalCount).toBe(2);
});
test("should return the document when searched with their previous titles", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
teamId: team.id,
userId: user.id,
});
const document = await buildDocument({
teamId: team.id,
userId: user.id,
collectionId: collection.id,
title: "test number 1",
});
document.title = "change";
await document.save();
const { totalCount } = await SearchHelper.searchForUser(
user,
"test number"
);
expect(totalCount).toBe(1);
});
test("should not return the document when searched with neither the titles nor the previous titles", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
teamId: team.id,
userId: user.id,
});
const document = await buildDocument({
teamId: team.id,
userId: user.id,
collectionId: collection.id,
title: "test number 1",
});
document.title = "change";
await document.save();
const { totalCount } = await SearchHelper.searchForUser(
user,
"title doesn't exist"
);
expect(totalCount).toBe(0);
});
});
describe("#searchTitlesForUser", () => {
test("should return search results from collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
userId: user.id,
teamId: team.id,
});
const document = await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection.id,
title: "test",
});
const documents = await SearchHelper.searchTitlesForUser(user, "test");
expect(documents.length).toBe(1);
expect(documents[0]?.id).toBe(document.id);
});
test("should filter to specific collection", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection = await buildCollection({
userId: user.id,
teamId: team.id,
});
const collection1 = await buildCollection({
userId: user.id,
teamId: team.id,
});
const document = await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection.id,
title: "test",
});
await buildDraftDocument({
teamId: team.id,
userId: user.id,
title: "test",
});
await buildDocument({
userId: user.id,
teamId: team.id,
collectionId: collection1.id,
title: "test",
});
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
collectionId: collection.id,
});
expect(documents.length).toBe(1);
expect(documents[0]?.id).toBe(document.id);
});
test("should handle no collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const documents = await SearchHelper.searchTitlesForUser(user, "test");
expect(documents.length).toBe(0);
});
test("should search only drafts created by user", async () => {
const user = await buildUser();
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
includeDrafts: true,
});
expect(documents.length).toBe(1);
});
test("should not include drafts", async () => {
const user = await buildUser();
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
includeDrafts: false,
});
expect(documents.length).toBe(0);
});
test("should include results from drafts as well", async () => {
const user = await buildUser();
await buildDocument({
userId: user.id,
teamId: user.teamId,
createdById: user.id,
title: "not test",
});
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
includeDrafts: true,
});
expect(documents.length).toBe(2);
});
test("should not include results from drafts", async () => {
const user = await buildUser();
await buildDocument({
userId: user.id,
teamId: user.teamId,
createdById: user.id,
title: "not test",
});
await buildDraftDocument({
teamId: user.teamId,
userId: user.id,
createdById: user.id,
title: "test",
});
const documents = await SearchHelper.searchTitlesForUser(user, "test", {
includeDrafts: false,
});
expect(documents.length).toBe(1);
});
});
describe("webSearchQuery", () => {
test("should correctly sanitize query", () => {
expect(SearchHelper.webSearchQuery("one/two")).toBe("one/two:*");
expect(SearchHelper.webSearchQuery("one\\two")).toBe("one\\\\two:*");
expect(SearchHelper.webSearchQuery("test''")).toBe("test");
});
test("should wildcard single word queries", () => {
expect(SearchHelper.webSearchQuery("test")).toBe("test:*");
expect(SearchHelper.webSearchQuery("'")).toBe("");
expect(SearchHelper.webSearchQuery("'quoted'")).toBe(`"quoted":*`);
});
test("should not wildcard other queries", () => {
expect(SearchHelper.webSearchQuery("this is a test")).toBe(
"this&is&a&test"
);
});
});
});