Spaces:
Sleeping
Sleeping
File size: 2,895 Bytes
0d57d79 d594446 0d57d79 874bd3c 0d57d79 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { LocalFsStore } from '../../../src/lib/server/storage/local-fs-store';
import { PrefixedStore } from '../../../src/lib/server/storage/prefixed-store';
import { decodeText, encodeText } from '../../../src/lib/server/storage/store';
describe('PrefixedStore', () => {
let dir: string;
let inner: LocalFsStore;
let a: PrefixedStore;
let b: PrefixedStore;
beforeEach(async () => {
dir = await mkdtemp(join(tmpdir(), 'splitwise-prefixed-'));
inner = new LocalFsStore(dir);
a = new PrefixedStore(inner, 'tenants/a');
b = new PrefixedStore(inner, '/tenants/b/');
});
afterEach(() => rm(dir, { recursive: true, force: true }));
it('stores objects under the prefix and reads them back', async () => {
await a.put('users.json', encodeText('A'), 'application/json');
expect(decodeText((await a.get('users.json'))!)).toBe('A');
expect(decodeText((await inner.get('tenants/a/users.json'))!)).toBe('A');
expect(await b.get('users.json')).toBeNull();
});
it('lists with the prefix stripped and never leaks other prefixes', async () => {
await a.put('events/1.json', encodeText('1'));
await a.put('events/2.json', encodeText('2'));
await b.put('events/9.json', encodeText('9'));
expect((await a.list('events/')).map((e) => e.path)).toEqual(['events/1.json', 'events/2.json']);
expect((await a.list('')).map((e) => e.path)).toEqual(['events/1.json', 'events/2.json']);
expect((await b.list('events/')).map((e) => e.path)).toEqual(['events/9.json']);
});
it('rejects a list prefix without a trailing slash', async () => {
await expect(a.list('events')).rejects.toThrow(/Invalid prefix/);
});
it('appends, copies and deletes inside the prefix', async () => {
await a.append('ledger.jsonl', encodeText('x\n'));
await a.append('ledger.jsonl', encodeText('y\n'));
await a.copy('ledger.jsonl', 'backups/l.jsonl');
expect(decodeText((await inner.get('tenants/a/backups/l.jsonl'))!)).toBe('x\ny\n');
await a.delete(['ledger.jsonl', 'missing.txt']);
expect(await a.get('ledger.jsonl')).toBeNull();
});
it('wipe removes only its own subtree', async () => {
await a.put('x.txt', encodeText('x'));
await a.put('deep/y.txt', encodeText('y'));
await b.put('z.txt', encodeText('z'));
expect(await a.wipe()).toBe(2);
expect(await a.list('')).toEqual([]);
expect(await b.get('z.txt')).not.toBeNull();
});
it('rejects invalid prefixes', () => {
expect(() => new PrefixedStore(inner, '')).toThrow();
expect(() => new PrefixedStore(inner, '../x')).toThrow();
});
it('rejects paths that escape the prefix', async () => {
await expect(a.get('../b/z.txt')).rejects.toThrow();
await expect(a.put('../../x', encodeText('x'))).rejects.toThrow();
});
});
|