Files
outline/server/queues/processors/DocumentArchivedProcessor.test.ts
T
Tom Moor b91d9e9a72 feat: Extract search into pluggable provider system (#11448)
* feat: Extract search into pluggable provider system

Refactors the monolithic SearchHelper into a pluggable search provider
architecture, enabling alternative search backends (Elasticsearch,
Turbopuffer, etc.) while preserving PostgreSQL full-text search as the
default. The SEARCH_PROVIDER env var selects the active provider.

- Add BaseSearchProvider abstract class and SearchProviderManager
- Add Hook.SearchProvider to the plugin system
- Move PostgreSQL search logic into plugins/postgres-search/
- Add SearchIndexProcessor for event-driven index sync
- Update all callers to use the provider manager directly
- Keep SearchHelper as a deprecated thin wrapper for backwards compat

Closes #11347

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: Remove deprecated SearchHelper wrapper

All callers now use SearchProviderManager directly, so the thin
delegation wrapper is no longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: Rename postgres-search plugin to search-postgres

Renames the plugin folder and id so that future search provider plugins
(e.g. search-elasticsearch, search-turbopuffer) will be colocated
alphabetically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: Remove special-case plugin import from SearchProviderManager

Make PluginManager.loadPlugins resilient to individual plugin load
failures so SearchProviderManager can use the standard getHooks path
without needing to directly import the search-postgres plugin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: Add missing search provider tests for full coverage parity

Adds all tests that existed in the old SearchHelper.test.ts but were missing
from PostgresSearchProvider.test.ts, including searchTitlesForUser status
filters, collection filtering, group memberships, and sorting tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feedback

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 23:01:26 -04:00

149 lines
3.6 KiB
TypeScript

import { Star } from "@server/models";
import { buildDocument, buildStar, buildUser } from "@server/test/factories";
import DocumentArchivedProcessor from "./DocumentArchivedProcessor";
const ip = "127.0.0.1";
describe("DocumentArchivedProcessor", () => {
test("should remove document from actor's starred documents", async () => {
const user = await buildUser();
const document = await buildDocument({
teamId: user.teamId,
userId: user.id,
});
// Create a star for the document by the user
await buildStar({
userId: user.id,
documentId: document.id,
});
// Verify the star exists
expect(
await Star.count({
where: {
userId: user.id,
documentId: document.id,
},
})
).toBe(1);
// Run the processor
const processor = new DocumentArchivedProcessor();
await processor.perform({
name: "documents.archive",
documentId: document.id,
collectionId: document.collectionId!,
actorId: user.id,
teamId: user.teamId,
ip,
});
// Verify the star has been removed
expect(
await Star.count({
where: {
userId: user.id,
documentId: document.id,
},
})
).toBe(0);
});
test("should not remove document from other users' starred documents", async () => {
const actor = await buildUser();
const otherUser = await buildUser({ teamId: actor.teamId });
const document = await buildDocument({
teamId: actor.teamId,
userId: actor.id,
});
// Create stars for both users
await buildStar({
userId: actor.id,
documentId: document.id,
});
await buildStar({
userId: otherUser.id,
documentId: document.id,
});
// Verify both stars exist
expect(
await Star.count({
where: {
documentId: document.id,
},
})
).toBe(2);
// Run the processor (actor archives the document)
const processor = new DocumentArchivedProcessor();
await processor.perform({
name: "documents.archive",
documentId: document.id,
collectionId: document.collectionId!,
actorId: actor.id,
teamId: actor.teamId,
ip,
});
// Verify only the actor's star has been removed
expect(
await Star.count({
where: {
userId: actor.id,
documentId: document.id,
},
})
).toBe(0);
// Verify the other user's star still exists
expect(
await Star.count({
where: {
userId: otherUser.id,
documentId: document.id,
},
})
).toBe(1);
});
test("should not fail if document is not starred by actor", async () => {
const user = await buildUser();
const document = await buildDocument({
teamId: user.teamId,
userId: user.id,
});
// Do not create a star for the document
// Verify the star doesn't exist
expect(
await Star.count({
where: {
userId: user.id,
documentId: document.id,
},
})
).toBe(0);
// Run the processor (should not fail)
const processor = new DocumentArchivedProcessor();
await processor.perform({
name: "documents.archive",
documentId: document.id,
collectionId: document.collectionId!,
actorId: user.id,
teamId: user.teamId,
ip,
});
// Verify the star still doesn't exist (no error occurred)
expect(
await Star.count({
where: {
userId: user.id,
documentId: document.id,
},
})
).toBe(0);
});
});