fix: Server error when search term contains double single quotes
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"build": "yarn clean && yarn vite:build && yarn build:i18n && yarn build:server",
|
||||
"start": "node ./build/server/index.js",
|
||||
"dev": "NODE_ENV=development yarn concurrently -n api,collaboration -c \"blue,magenta\" \"node --inspect=0.0.0.0 build/server/index.js --services=cron,collaboration,websockets,admin,web,worker\"",
|
||||
"dev:backend": "NODE_ENV=development nodemon --exec \"yarn build:server && yarn dev\" -e js,ts,tsx --ignore build/ --ignore app/ --ignore shared/editor --ignore server/migrations",
|
||||
"dev:backend": "NODE_ENV=development nodemon --exec \"yarn build:server && yarn dev\" -e js,ts,tsx --ignore *.test.ts --ignore build/ --ignore app/ --ignore shared/editor --ignore server/migrations",
|
||||
"dev:watch": "NODE_ENV=development yarn concurrently -n backend,frontend \"yarn dev:backend\" \"yarn vite:dev\"",
|
||||
"lint": "eslint app server shared plugins",
|
||||
"prepare": "husky install",
|
||||
|
||||
@@ -12,444 +12,464 @@ beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("#searchForTeam", () => {
|
||||
test("should return search results from public collections", async () => {
|
||||
const team = await buildTeam();
|
||||
const collection = await buildCollection({
|
||||
teamId: team.id,
|
||||
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);
|
||||
});
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
collectionId: collection.id,
|
||||
title: "test",
|
||||
|
||||
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");
|
||||
});
|
||||
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,
|
||||
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",
|
||||
});
|
||||
const { results } = await SearchHelper.searchForUser(user, "test");
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].document?.id).toBe(document.id);
|
||||
});
|
||||
await buildDocument({
|
||||
teamId: team.id,
|
||||
collectionId: collection.id,
|
||||
title: "test",
|
||||
|
||||
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 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");
|
||||
});
|
||||
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,
|
||||
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);
|
||||
});
|
||||
await buildDocument({
|
||||
teamId: team.id,
|
||||
collectionId: collection.id,
|
||||
title: "test",
|
||||
|
||||
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);
|
||||
});
|
||||
const { results } = await SearchHelper.searchForTeam(team, "test", {
|
||||
collectionId: collection.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);
|
||||
});
|
||||
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,
|
||||
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");
|
||||
});
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
collectionId: collection.id,
|
||||
title: "test 1",
|
||||
test("should wildcard single word queries", () => {
|
||||
expect(SearchHelper.webSearchQuery("test")).toBe("test:*");
|
||||
expect(SearchHelper.webSearchQuery("'")).toBe("");
|
||||
expect(SearchHelper.webSearchQuery("'quoted'")).toBe(`"quoted":*`);
|
||||
});
|
||||
await buildDocument({
|
||||
teamId: team.id,
|
||||
collectionId: collection.id,
|
||||
title: "test 2",
|
||||
test("should not wildcard other queries", () => {
|
||||
expect(SearchHelper.webSearchQuery("this is a test")).toBe(
|
||||
"this&is&a&test"
|
||||
);
|
||||
});
|
||||
|
||||
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",
|
||||
});
|
||||
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 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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -427,7 +427,7 @@ export default class SearchHelper {
|
||||
* @param query The user search query
|
||||
* @returns The query formatted for Postgres ts_query
|
||||
*/
|
||||
private static webSearchQuery(query: string): string {
|
||||
public static webSearchQuery(query: string): string {
|
||||
// limit length of search queries as we're using regex against untrusted input
|
||||
let limitedQuery = this.escapeQuery(query.slice(0, this.maxQueryLength));
|
||||
|
||||
@@ -439,7 +439,7 @@ export default class SearchHelper {
|
||||
!limitedQuery.endsWith('"');
|
||||
|
||||
// Replace single quote characters with &.
|
||||
const singleQuotes = limitedQuery.matchAll(/'/g);
|
||||
const singleQuotes = limitedQuery.matchAll(/'+/g);
|
||||
|
||||
for (const match of singleQuotes) {
|
||||
if (
|
||||
@@ -454,8 +454,10 @@ export default class SearchHelper {
|
||||
}
|
||||
}
|
||||
|
||||
return queryParser()(
|
||||
singleUnquotedSearch ? `${limitedQuery}*` : limitedQuery
|
||||
return (
|
||||
queryParser()(singleUnquotedSearch ? `${limitedQuery}*` : limitedQuery)
|
||||
// Remove any trailing join characters
|
||||
.replace(/&$/, "")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user