Fix collection filter returning documents from all collections when no search query (#10775)

* Initial plan

* Fix search filtering by collection to exclude other collections

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* Add API-level test for collection filtering without search term

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* fix: Private collections

* refactor

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
This commit is contained in:
Copilot
2025-12-03 20:49:57 -05:00
committed by GitHub
parent 55f21bfbb3
commit d02f35b770
3 changed files with 103 additions and 0 deletions

View File

@@ -295,6 +295,54 @@ describe("SearchHelper", () => {
);
});
it("should not return documents from other collections when filtering by specific collection without search term", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });
const collection1 = await buildCollection({
teamId: team.id,
userId: user.id,
});
const collection2 = await buildCollection({
teamId: team.id,
userId: user.id,
});
const docsInCollection1 = await Promise.all([
buildDocument({
teamId: team.id,
userId: user.id,
collectionId: collection1.id,
title: "document 1 in collection 1",
}),
buildDocument({
teamId: team.id,
userId: user.id,
collectionId: collection1.id,
title: "document 2 in collection 1",
}),
]);
await Promise.all([
buildDocument({
teamId: team.id,
userId: user.id,
collectionId: collection2.id,
title: "document 1 in collection 2",
}),
buildDocument({
teamId: team.id,
userId: user.id,
collectionId: collection2.id,
title: "document 2 in collection 2",
}),
]);
const { results } = await SearchHelper.searchForUser(user, {
collectionId: collection1.id,
});
expect(results.length).toBe(2);
expect(results.map((r) => r.document.id).sort()).toEqual(
docsInCollection1.map((doc) => doc.id).sort()
);
});
it("should handle no collections", async () => {
const team = await buildTeam();
const user = await buildUser({ teamId: team.id });

View File

@@ -574,6 +574,9 @@ export default class SearchHelper {
? [options.collectionId]
: await model.collectionIds();
if (options.collectionId) {
where[Op.and].push({ collectionId: options.collectionId });
}
if (collectionIds.length) {
where[Op.or].push({ collectionId: collectionIds });
}

View File

@@ -1933,6 +1933,58 @@ describe("#documents.search", () => {
expect(body.data[0].document.id).toEqual(document.id);
expect(res.status).toEqual(200);
});
it("should not return documents from other collections when filtering by specific collection without search term", async () => {
const user = await buildUser();
const collection1 = await buildCollection({
teamId: user.teamId,
userId: user.id,
});
const collection2 = await buildCollection({
teamId: user.teamId,
userId: user.id,
});
const docsInCollection1 = await Promise.all([
buildDocument({
teamId: user.teamId,
userId: user.id,
collectionId: collection1.id,
title: "document 1 in collection 1",
}),
buildDocument({
teamId: user.teamId,
userId: user.id,
collectionId: collection1.id,
title: "document 2 in collection 1",
}),
]);
await Promise.all([
buildDocument({
teamId: user.teamId,
userId: user.id,
collectionId: collection2.id,
title: "document 1 in collection 2",
}),
buildDocument({
teamId: user.teamId,
userId: user.id,
collectionId: collection2.id,
title: "document 2 in collection 2",
}),
]);
const res = await server.post("/api/documents.search", {
body: {
token: user.getJwtToken(),
collectionId: collection1.id,
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data).toHaveLength(2);
const returnedIds = body.data.map((d: any) => d.document.id).sort();
const expectedIds = docsInCollection1.map((d) => d.id).sort();
expect(returnedIds).toEqual(expectedIds);
});
});
describe("#documents.templatize", () => {