198 lines
6.3 KiB
TypeScript
198 lines
6.3 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { createMinioIngestTool, minioIngestInternals } from './minio-ingest.js';
|
|
import type { BackupConfig } from '../../config/schema.js';
|
|
import type { MemoryStore } from '../../memory/store.js';
|
|
|
|
function makeBackupConfig(overrides?: Partial<BackupConfig>): BackupConfig {
|
|
return {
|
|
enabled: true,
|
|
schedule: undefined,
|
|
interval: '24h',
|
|
run_on_start: false,
|
|
notify: undefined,
|
|
failure_threshold: 1,
|
|
notify_recovery: true,
|
|
local_dir: '~/.local/share/flynn/backups',
|
|
include_vectors: true,
|
|
minio: {
|
|
enabled: true,
|
|
endpoint: 'localhost:9000',
|
|
access_key: 'minio-admin',
|
|
secret_key: 'minio-secret',
|
|
bucket: 'flynn-knowledge',
|
|
prefix: 'flynn',
|
|
secure: false,
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('minio ingest internals', () => {
|
|
it('accepts known text-like extensions', () => {
|
|
expect(minioIngestInternals.isLikelyTextObject('notes/today.md')).toBe(true);
|
|
expect(minioIngestInternals.isLikelyTextObject('logs/daemon.log')).toBe(true);
|
|
expect(minioIngestInternals.isLikelyTextObject('manual.pdf')).toBe(true);
|
|
expect(minioIngestInternals.isLikelyTextObject('manual.docx')).toBe(true);
|
|
});
|
|
|
|
it('rejects likely binary extensions', () => {
|
|
expect(minioIngestInternals.isLikelyTextObject('manual.exe')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('createMinioIngestTool', () => {
|
|
it('ingests object and writes to memory', async () => {
|
|
const write = vi.fn();
|
|
const store = { write } as unknown as MemoryStore;
|
|
const execRunner = vi.fn(async () => ({
|
|
stdout: '# Runbook\n\nRestart service before deploy.\n',
|
|
stderr: '',
|
|
}));
|
|
|
|
const tool = createMinioIngestTool(makeBackupConfig(), store, {
|
|
execRunner,
|
|
now: () => new Date('2026-02-16T15:00:00.000Z'),
|
|
});
|
|
|
|
const result = await tool.execute({
|
|
object_key: 'knowledge/runbook.md',
|
|
namespace: 'global/runbooks',
|
|
mode: 'append',
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.output).toContain('Ingested MinIO object');
|
|
expect(write).toHaveBeenCalledWith(
|
|
'global/runbooks',
|
|
expect.stringContaining('source: minio://flynn-knowledge/knowledge/runbook.md'),
|
|
'append',
|
|
);
|
|
expect(execRunner).toHaveBeenCalledWith(
|
|
'mc',
|
|
['cat', 'flynningest/flynn-knowledge/knowledge/runbook.md'],
|
|
expect.objectContaining({ env: expect.any(Object) }),
|
|
);
|
|
});
|
|
|
|
it('rejects unsupported binary object unless force=true', async () => {
|
|
const write = vi.fn();
|
|
const store = { write } as unknown as MemoryStore;
|
|
const execRunner = vi.fn();
|
|
const tool = createMinioIngestTool(makeBackupConfig(), store, { execRunner });
|
|
|
|
const result = await tool.execute({ object_key: 'knowledge/diagram.exe' });
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('Unsupported object type');
|
|
expect(execRunner).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('extracts PDF text with pdftotext', async () => {
|
|
const write = vi.fn();
|
|
const store = { write } as unknown as MemoryStore;
|
|
const execRunner = vi.fn(async (_file: string, args: string[]) => {
|
|
if (args[0] === 'cp') {
|
|
return { stdout: '', stderr: '' };
|
|
}
|
|
if (_file === 'pdftotext') {
|
|
return { stdout: 'Extracted PDF text', stderr: '' };
|
|
}
|
|
return { stdout: '', stderr: '' };
|
|
});
|
|
const tool = createMinioIngestTool(makeBackupConfig(), store, { execRunner });
|
|
|
|
const result = await tool.execute({
|
|
object_key: 'knowledge/diagram.pdf',
|
|
mode: 'replace',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(write).toHaveBeenCalledWith(
|
|
'global/knowledge',
|
|
expect.stringContaining('Extracted PDF text'),
|
|
'replace',
|
|
);
|
|
expect(execRunner).toHaveBeenCalledWith(
|
|
'mc',
|
|
expect.arrayContaining(['cp', 'flynningest/flynn-knowledge/knowledge/diagram.pdf']),
|
|
expect.objectContaining({ env: expect.any(Object) }),
|
|
);
|
|
expect(execRunner).toHaveBeenCalledWith(
|
|
'pdftotext',
|
|
expect.arrayContaining(['-q']),
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('extracts DOCX text with pandoc', async () => {
|
|
const write = vi.fn();
|
|
const store = { write } as unknown as MemoryStore;
|
|
const execRunner = vi.fn(async (_file: string, args: string[]) => {
|
|
if (_file === 'mc' && args[0] === 'cp') {
|
|
return { stdout: '', stderr: '' };
|
|
}
|
|
if (_file === 'pandoc') {
|
|
return { stdout: 'Extracted DOCX text', stderr: '' };
|
|
}
|
|
return { stdout: '', stderr: '' };
|
|
});
|
|
const tool = createMinioIngestTool(makeBackupConfig(), store, { execRunner });
|
|
|
|
const result = await tool.execute({
|
|
object_key: 'knowledge/spec.docx',
|
|
mode: 'replace',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(write).toHaveBeenCalledWith(
|
|
'global/knowledge',
|
|
expect.stringContaining('Extracted DOCX text'),
|
|
'replace',
|
|
);
|
|
expect(execRunner).toHaveBeenCalledWith(
|
|
'pandoc',
|
|
expect.arrayContaining(['-t', 'plain']),
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('allows non-text extension when force=true', async () => {
|
|
const write = vi.fn();
|
|
const store = { write } as unknown as MemoryStore;
|
|
const execRunner = vi.fn(async () => ({
|
|
stdout: 'PDF text extracted upstream',
|
|
stderr: '',
|
|
}));
|
|
const tool = createMinioIngestTool(makeBackupConfig(), store, { execRunner });
|
|
|
|
const result = await tool.execute({
|
|
object_key: 'knowledge/diagram.pdf',
|
|
force: true,
|
|
mode: 'replace',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
expect(write).toHaveBeenCalledWith(
|
|
'global/knowledge',
|
|
expect.stringContaining('PDF text extracted upstream'),
|
|
'replace',
|
|
);
|
|
});
|
|
|
|
it('returns an error when minio is disabled', async () => {
|
|
const write = vi.fn();
|
|
const store = { write } as unknown as MemoryStore;
|
|
const tool = createMinioIngestTool(makeBackupConfig({
|
|
minio: {
|
|
enabled: false,
|
|
endpoint: undefined,
|
|
access_key: undefined,
|
|
secret_key: undefined,
|
|
bucket: undefined,
|
|
prefix: 'flynn',
|
|
secure: true,
|
|
},
|
|
}), store);
|
|
|
|
const result = await tool.execute({ object_key: 'notes/today.md' });
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('backup.minio.enabled=true');
|
|
});
|
|
});
|