This commit is contained in:
154
packages/server/tests/local/files-indexer.test.ts
Normal file
154
packages/server/tests/local/files-indexer.test.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* File Indexer Integration Tests (L-20)
|
||||
*
|
||||
* Verifies that file-route mutations call into the workspace mind's FileIndexer:
|
||||
* POST /upload (base64 JSON path) indexes a new .md/.txt
|
||||
* POST /delete removes the index row + frame
|
||||
* POST /move updates the index row path
|
||||
* POST /copy indexes the destination
|
||||
*
|
||||
* The indexer itself is unit-tested in packages/core/tests/file-indexer.test.ts —
|
||||
* this file only confirms the wiring.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { MindDB, SessionStore, FrameStore, FileIndexer } from '@waggle/core';
|
||||
import { buildLocalServer } from '../../src/local/index.js';
|
||||
import { injectWithAuth } from '../test-utils.js';
|
||||
|
||||
describe('File Indexer — integration with /files routes (L-20)', () => {
|
||||
let server: FastifyInstance;
|
||||
let tmpDir: string;
|
||||
let workspaceId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-files-indexer-'));
|
||||
const mind = new MindDB(path.join(tmpDir, 'personal.mind'));
|
||||
const sessions = new SessionStore(mind);
|
||||
const frames = new FrameStore(mind);
|
||||
const s = sessions.create('files-indexer-seed');
|
||||
frames.createIFrame(s.gop_id, 'seed', 'normal');
|
||||
mind.close();
|
||||
|
||||
server = await buildLocalServer({ dataDir: tmpDir });
|
||||
|
||||
const create = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: '/api/workspaces',
|
||||
payload: { name: 'Indexer test', group: 'Test' },
|
||||
});
|
||||
workspaceId = create.json().id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.close();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
const prefix = () => `/api/workspaces/${workspaceId}/files`;
|
||||
|
||||
/** Reach the same workspace mind the route handlers use (via mindCache). */
|
||||
function getIndexer(): FileIndexer {
|
||||
const mind = server.mindCache.getOrOpen(workspaceId);
|
||||
if (!mind) throw new Error('workspace mind unavailable');
|
||||
return new FileIndexer(mind);
|
||||
}
|
||||
|
||||
function getFrameStore(): FrameStore {
|
||||
const mind = server.mindCache.getOrOpen(workspaceId);
|
||||
if (!mind) throw new Error('workspace mind unavailable');
|
||||
return new FrameStore(mind);
|
||||
}
|
||||
|
||||
async function uploadViaJson(name: string, content: string, dir = '/notes') {
|
||||
return injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: `${prefix()}/upload`,
|
||||
payload: { path: dir, name, data: Buffer.from(content).toString('base64') },
|
||||
});
|
||||
}
|
||||
|
||||
it('indexes a .md file on upload', async () => {
|
||||
const res = await uploadViaJson('indexed.md', '# indexed\n\nbody text');
|
||||
expect(res.statusCode).toBe(201);
|
||||
|
||||
const row = getIndexer().getRow('/notes/indexed.md');
|
||||
expect(row).toBeTruthy();
|
||||
expect(row!.frameId).toBeGreaterThan(0);
|
||||
const frame = getFrameStore().getById(row!.frameId);
|
||||
expect(frame?.content).toContain('# indexed');
|
||||
expect(frame?.content).toContain('/notes/indexed.md');
|
||||
});
|
||||
|
||||
it('skips non-indexable extensions silently (pdf)', async () => {
|
||||
const res = await uploadViaJson('skipped.pdf', 'PDF bytes (fake)');
|
||||
expect(res.statusCode).toBe(201); // upload still works
|
||||
expect(getIndexer().getRow('/notes/skipped.pdf')).toBeNull();
|
||||
});
|
||||
|
||||
it('removes the index row on delete', async () => {
|
||||
await uploadViaJson('doomed.md', 'bye');
|
||||
expect(getIndexer().getRow('/notes/doomed.md')).toBeTruthy();
|
||||
|
||||
const del = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: `${prefix()}/delete`,
|
||||
payload: { path: '/notes/doomed.md' },
|
||||
});
|
||||
expect(del.statusCode).toBe(204);
|
||||
|
||||
expect(getIndexer().getRow('/notes/doomed.md')).toBeNull();
|
||||
});
|
||||
|
||||
it('updates the path on move', async () => {
|
||||
await uploadViaJson('movable.md', 'contents');
|
||||
const move = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: `${prefix()}/move`,
|
||||
payload: { from: '/notes/movable.md', to: '/notes/moved.md' },
|
||||
});
|
||||
expect(move.statusCode).toBe(200);
|
||||
|
||||
const indexer = getIndexer();
|
||||
expect(indexer.getRow('/notes/movable.md')).toBeNull();
|
||||
expect(indexer.getRow('/notes/moved.md')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('indexes the destination on copy', async () => {
|
||||
await uploadViaJson('source.md', 'copy me');
|
||||
const copy = await injectWithAuth(server, {
|
||||
method: 'POST',
|
||||
url: `${prefix()}/copy`,
|
||||
payload: { from: '/notes/source.md', to: '/notes/copy.md' },
|
||||
});
|
||||
expect(copy.statusCode).toBe(200);
|
||||
|
||||
const indexer = getIndexer();
|
||||
expect(indexer.getRow('/notes/source.md')).toBeTruthy();
|
||||
expect(indexer.getRow('/notes/copy.md')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('re-indexes on overwrite (upload same path with different content)', async () => {
|
||||
await uploadViaJson('overwrite.md', 'first');
|
||||
const firstRow = getIndexer().getRow('/notes/overwrite.md')!;
|
||||
|
||||
await uploadViaJson('overwrite.md', 'second');
|
||||
const secondRow = getIndexer().getRow('/notes/overwrite.md')!;
|
||||
expect(secondRow.frameId).not.toBe(firstRow.frameId);
|
||||
expect(secondRow.contentHash).not.toBe(firstRow.contentHash);
|
||||
});
|
||||
|
||||
it('skips re-indexing when content is unchanged', async () => {
|
||||
await uploadViaJson('same.md', 'same');
|
||||
const firstRow = getIndexer().getRow('/notes/same.md')!;
|
||||
|
||||
await uploadViaJson('same.md', 'same');
|
||||
const secondRow = getIndexer().getRow('/notes/same.md')!;
|
||||
expect(secondRow.frameId).toBe(firstRow.frameId);
|
||||
expect(secondRow.contentHash).toBe(firstRow.contentHash);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user