mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-26 07:06:08 +00:00
fix: events hardening (#3814)
This commit is contained in:
@@ -986,6 +986,95 @@ describe('FSEntryStore timestamps and updates', () => {
|
||||
thumbnail: 'data:image/png;base64,BB',
|
||||
});
|
||||
});
|
||||
|
||||
it('updateEntry returns and caches the primary row when the replica lags', async () => {
|
||||
const user = await makeUser();
|
||||
const file = await createFile(user, `${user.home}/Documents/stale.txt`);
|
||||
|
||||
// Pre-update row, captured before the patch — stands in for what a
|
||||
// lagging replica would still be serving.
|
||||
const staleRows = (await server.clients.db.read(
|
||||
'SELECT * FROM fsentries WHERE uuid = ?',
|
||||
[file.uuid],
|
||||
)) as Array<Record<string, unknown>>;
|
||||
for (const row of staleRows) row.subdomains_agg = null;
|
||||
|
||||
const db = server.clients.db;
|
||||
const originalTryHardRead = (Object.getPrototypeOf(db) as typeof db)
|
||||
.tryHardRead;
|
||||
const tryHardReadSpy = vi
|
||||
.spyOn(db, 'tryHardRead')
|
||||
.mockImplementation(async (query: string, params: unknown[] = []) => {
|
||||
if (
|
||||
query.includes('WHERE uuid = ? LIMIT 1') &&
|
||||
params[0] === file.uuid
|
||||
) {
|
||||
return staleRows;
|
||||
}
|
||||
return originalTryHardRead.call(db, query, params);
|
||||
});
|
||||
try {
|
||||
|
||||
const newPath = `${user.home}/Documents/renamed.txt`;
|
||||
const updated = await store.updateEntry(file.uuid, {
|
||||
name: 'renamed.txt',
|
||||
path: newPath,
|
||||
});
|
||||
|
||||
expect(updated.name).toBe('renamed.txt');
|
||||
expect(updated.path).toBe(newPath);
|
||||
await expect(store.getEntryByUuid(file.uuid)).resolves.toMatchObject({
|
||||
path: newPath,
|
||||
});
|
||||
expect(tryHardReadSpy).not.toHaveBeenCalled();
|
||||
|
||||
} finally {
|
||||
tryHardReadSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('updateEntryThumbnailByUuidForUser returns the primary thumbnail when the replica lags', async () => {
|
||||
const owner = await makeUser();
|
||||
const file = await createFile(
|
||||
owner,
|
||||
`${owner.home}/Documents/stale-thumb.txt`,
|
||||
);
|
||||
|
||||
const staleRows = (await server.clients.db.read(
|
||||
'SELECT * FROM fsentries WHERE uuid = ?',
|
||||
[file.uuid],
|
||||
)) as Array<Record<string, unknown>>;
|
||||
for (const row of staleRows) row.subdomains_agg = null;
|
||||
|
||||
const db = server.clients.db;
|
||||
const originalTryHardRead = (Object.getPrototypeOf(db) as typeof db)
|
||||
.tryHardRead;
|
||||
const tryHardReadSpy = vi
|
||||
.spyOn(db, 'tryHardRead')
|
||||
.mockImplementation(async (query: string, params: unknown[] = []) => {
|
||||
if (
|
||||
query.includes('AND user_id = ?') &&
|
||||
params[0] === file.uuid
|
||||
) {
|
||||
return staleRows;
|
||||
}
|
||||
return originalTryHardRead.call(db, query, params);
|
||||
});
|
||||
try {
|
||||
|
||||
const updated = await store.updateEntryThumbnailByUuidForUser(
|
||||
owner.userId,
|
||||
file.uuid,
|
||||
'data:image/png;base64,NEW',
|
||||
);
|
||||
|
||||
expect(updated.thumbnail).toBe('data:image/png;base64,NEW');
|
||||
expect(tryHardReadSpy).not.toHaveBeenCalled();
|
||||
|
||||
} finally {
|
||||
tryHardReadSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('FSEntryStore listing and pagination', () => {
|
||||
|
||||
@@ -1384,7 +1384,7 @@ export class FSEntryStore extends PuterStore {
|
||||
}
|
||||
}
|
||||
|
||||
const refreshedRows = (await this.clients.db.tryHardRead(
|
||||
const refreshedRows = (await this.clients.db.pread(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE uuid = ? AND user_id = ? LIMIT 1`,
|
||||
[uuid, userId],
|
||||
)) as unknown as FSEntryRow[];
|
||||
@@ -1950,7 +1950,7 @@ export class FSEntryStore extends PuterStore {
|
||||
.join(', ');
|
||||
// By uuid alone — the rows just written belong to the
|
||||
// parent's owner, not necessarily the acting user.
|
||||
const rows = (await this.clients.db.tryHardRead(
|
||||
const rows = (await this.clients.db.pread(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE uuid IN (${placeholders})`,
|
||||
insertUuidChunk,
|
||||
)) as unknown as FSEntryRow[];
|
||||
@@ -2359,7 +2359,7 @@ export class FSEntryStore extends PuterStore {
|
||||
],
|
||||
);
|
||||
|
||||
const rows = (await this.clients.db.tryHardRead(
|
||||
const rows = (await this.clients.db.pread(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE uuid = ? LIMIT 1`,
|
||||
[uuid],
|
||||
)) as unknown as FSEntryRow[];
|
||||
@@ -2413,7 +2413,7 @@ export class FSEntryStore extends PuterStore {
|
||||
// Re-read the row itself rather than going through `getEntryByUuid`:
|
||||
// that read is cache-first and would hand back the pre-touch
|
||||
// timestamps (and then re-cache them for another TTL).
|
||||
const refreshedRows = (await this.clients.db.tryHardRead(
|
||||
const refreshedRows = (await this.clients.db.pread(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE uuid = ? LIMIT 1`,
|
||||
[uuid],
|
||||
)) as unknown as FSEntryRow[];
|
||||
@@ -2859,7 +2859,9 @@ export class FSEntryStore extends PuterStore {
|
||||
[...values, uuid],
|
||||
);
|
||||
|
||||
const refreshedRows = (await this.clients.db.tryHardRead(
|
||||
// Read back from the primary: a lagging replica still holds the pre-update
|
||||
// row, so tryHardRead would return it (and cache it).
|
||||
const refreshedRows = (await this.clients.db.pread(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE uuid = ? LIMIT 1`,
|
||||
[uuid],
|
||||
)) as unknown as FSEntryRow[];
|
||||
@@ -2942,7 +2944,7 @@ export class FSEntryStore extends PuterStore {
|
||||
// rely on TTL (60s) to refresh — username rename is rare enough
|
||||
// that a broad subtree invalidation isn't worth the round-trips.
|
||||
await this.#invalidateEntryCache(root);
|
||||
const refreshedRows = (await this.clients.db.tryHardRead(
|
||||
const refreshedRows = (await this.clients.db.pread(
|
||||
`SELECT ${this.#selectFsentriesColumns()} FROM fsentries WHERE id = ? LIMIT 1`,
|
||||
[root.id],
|
||||
)) as unknown as FSEntryRow[];
|
||||
|
||||
Reference in New Issue
Block a user