mirror of
https://github.com/hanzoai/chat.git
synced 2026-09-30 20:51:17 +00:00
The first pass moved ingestion; these are the paths that still read RAG_API_URL,
which the universe change removes. Left as-is they would have silently degraded:
file_search and the context handler would stop retrieving, and deleting an S3 object
would orphan its embeddings.
- file_search (api/app/clients/tools/util/fileSearch.js) queries through RagClient;
`req` is threaded from handleTools (as `options.req`, the same value that file
already passes to resolveTenantBearer for images) so retrieval runs on the user's
own IAM bearer instead of a chat-minted short-lived token.
- createContextHandlers uses the client for both shapes (full context + top-k),
normalised to { data } so its callers are untouched.
- deleting an S3 object now drops the file's chunks via the same client rather than
the legacy deleteRagFile (which reads RAG_API_URL and would have no-op'd) —
best-effort, so a retrieval-side failure never blocks the object delete.
Dead code removed with the paths they served: the axios import and createQueryBody
body-builder in fileSearch, the deleteRagFile import in S3 crud.
Tests 11/11 (7 client + 4 key convention; the S3 spec mocks the client). Lint clean
on every touched file — the one remaining handleTools prettier error is pre-existing
(3 on clean main, 1 after this).
packages/api/src/files/{text,rag}.ts and app/checks.ts still name RAG_API_URL; all
three already guard on it and degrade gracefully (native text parsing, no-op delete,
a startup log), so they are inert once the var is gone — they come out with the
package's own pass rather than being half-migrated here.
58 lines
2.5 KiB
JavaScript
58 lines
2.5 KiB
JavaScript
// getS3Key is a pure function; stub the heavy transitive deps crud.js pulls in at
|
|
// require time (the SDK clients and the built @hanzochat/api bundle) so the key
|
|
// convention can be asserted without booting S3 or the agents runtime.
|
|
jest.mock('~/server/services/RagClient', () => ({ remove: jest.fn(), ragEnabled: () => true }), {
|
|
virtual: true,
|
|
});
|
|
jest.mock('@hanzochat/api', () => ({
|
|
initializeS3: jest.fn(),
|
|
deleteRagFile: jest.fn(),
|
|
isEnabled: () => false,
|
|
resolveRequestOrg: jest.fn(),
|
|
}));
|
|
jest.mock('@hanzochat/data-schemas', () => ({
|
|
logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
|
|
}));
|
|
jest.mock('@hanzochat/data-provider', () => ({ FileSources: { s3: 's3' } }));
|
|
jest.mock('@aws-sdk/s3-request-presigner', () => ({ getSignedUrl: jest.fn() }));
|
|
jest.mock('@aws-sdk/client-s3', () => ({
|
|
PutObjectCommand: jest.fn(),
|
|
GetObjectCommand: jest.fn(),
|
|
HeadObjectCommand: jest.fn(),
|
|
DeleteObjectCommand: jest.fn(),
|
|
}));
|
|
|
|
const { getS3Key } = require('./crud');
|
|
|
|
/**
|
|
* File storage is org-scoped in the shared bucket: the key carries the IAM org so
|
|
* one org's objects live in their own subtree. Pre-tenancy objects (and callers
|
|
* IAM gives no org) keep the historical flat key, which is why reads of existing
|
|
* files keep working.
|
|
*/
|
|
describe('getS3Key — IAM org scoping of the shared bucket', () => {
|
|
it('prefixes t/<org>/ when the request has an IAM org', () => {
|
|
expect(getS3Key('images', 'user1', 'a.png', 'acme')).toBe('t/acme/images/user1/a.png');
|
|
});
|
|
|
|
it('keeps the historical flat key when there is no org', () => {
|
|
expect(getS3Key('images', 'user1', 'a.png')).toBe('images/user1/a.png');
|
|
expect(getS3Key('images', 'user1', 'a.png', null)).toBe('images/user1/a.png');
|
|
expect(getS3Key('images', 'user1', 'a.png', '')).toBe('images/user1/a.png');
|
|
});
|
|
|
|
it('separates orgs — the whole point of the prefix', () => {
|
|
const a = getS3Key('images', 'user1', 'a.png', 'acme');
|
|
const b = getS3Key('images', 'user1', 'a.png', 'globex');
|
|
expect(a).not.toBe(b);
|
|
expect(a.startsWith('t/acme/')).toBe(true);
|
|
expect(b.startsWith('t/globex/')).toBe(true);
|
|
});
|
|
|
|
it('refuses an org that would escape its prefix', () => {
|
|
expect(() => getS3Key('images', 'user1', 'a.png', '../evil')).toThrow(/invalid tenantId/);
|
|
expect(() => getS3Key('images', 'user1', 'a.png', '..')).toThrow(/invalid tenantId/);
|
|
expect(() => getS3Key('images', 'user1', 'a.png', 'a/b')).toThrow(/invalid tenantId/);
|
|
});
|
|
});
|