Skip to content

Commit 40332f5

Browse files
committed
refactor(metadata): update MetadataProjector to use a scope object for organization and environment IDs
test: add localStorage shim for consistent testing behavior across environments
1 parent 93ad23c commit 40332f5

3 files changed

Lines changed: 61 additions & 11 deletions

File tree

apps/studio/test/ai-chat-panel.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ describe('use-ai-chat-panel', () => {
9797
});
9898

9999
it('does not throw when localStorage is unavailable', () => {
100-
const originalSetItem = Storage.prototype.setItem;
101-
Storage.prototype.setItem = () => { throw new Error('QuotaExceeded'); };
100+
const originalSetItem = localStorage.setItem.bind(localStorage);
101+
localStorage.setItem = () => { throw new Error('QuotaExceeded'); };
102102
expect(() => saveMessages([makeMsg({ id: '1', role: 'user', content: 'A' })])).not.toThrow();
103-
Storage.prototype.setItem = originalSetItem;
103+
localStorage.setItem = originalSetItem;
104104
});
105105
});
106106
});
@@ -260,10 +260,10 @@ describe('Agent Selector', () => {
260260
});
261261

262262
it('should not throw when localStorage is unavailable', () => {
263-
const originalSetItem = Storage.prototype.setItem;
264-
Storage.prototype.setItem = () => { throw new Error('QuotaExceeded'); };
263+
const originalSetItem = localStorage.setItem.bind(localStorage);
264+
localStorage.setItem = () => { throw new Error('QuotaExceeded'); };
265265
expect(() => saveSelectedAgent('metadata_assistant')).not.toThrow();
266-
Storage.prototype.setItem = originalSetItem;
266+
localStorage.setItem = originalSetItem;
267267
});
268268
});
269269

apps/studio/test/setup.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,55 @@
77

88
import '@testing-library/jest-dom/vitest';
99
import { cleanup } from '@testing-library/react';
10-
import { afterEach } from 'vitest';
10+
import { afterEach, beforeEach } from 'vitest';
11+
12+
// Node 22+ ships an experimental built-in `localStorage` that is a plain
13+
// empty object without Storage API methods. When `@vitest-environment
14+
// happy-dom` runs, that global can shadow happy-dom's Storage implementation,
15+
// leaving tests with `localStorage.getItem is not a function`. Install a
16+
// minimal in-memory Storage-compatible shim to guarantee consistent behavior
17+
// across Node versions and environments.
18+
function installStorageShim(): void {
19+
const store = new Map<string, string>();
20+
const shim = {
21+
get length() {
22+
return store.size;
23+
},
24+
clear(): void {
25+
store.clear();
26+
},
27+
getItem(key: string): string | null {
28+
return store.has(key) ? (store.get(key) as string) : null;
29+
},
30+
setItem(key: string, value: string): void {
31+
store.set(String(key), String(value));
32+
},
33+
removeItem(key: string): void {
34+
store.delete(key);
35+
},
36+
key(index: number): string | null {
37+
return Array.from(store.keys())[index] ?? null;
38+
},
39+
};
40+
Object.defineProperty(globalThis, 'localStorage', {
41+
configurable: true,
42+
writable: true,
43+
value: shim,
44+
});
45+
Object.defineProperty(globalThis, 'sessionStorage', {
46+
configurable: true,
47+
writable: true,
48+
value: { ...shim },
49+
});
50+
}
51+
52+
installStorageShim();
53+
54+
// Re-install before each test so tests that mutate localStorage.setItem
55+
// (to simulate quota errors) cannot leak across other tests.
56+
beforeEach(() => {
57+
installStorageShim();
58+
});
1159

1260
// Cleanup after each test
1361
afterEach(() => {

packages/metadata/src/projection/metadata-projector.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export interface MetadataProjectorOptions {
3838
export class MetadataProjector {
3939
private driver?: IDataDriver;
4040
private engine?: IDataEngine;
41-
private organizationId?: string;
42-
private environmentId?: string;
41+
/** Reserved for future multi-tenant projection scoping */
42+
readonly scope: { organizationId?: string; environmentId?: string };
4343

4444
// Map of metadata types to their target table names
4545
private readonly typeTableMap: Record<string, string> = {
@@ -57,8 +57,10 @@ export class MetadataProjector {
5757
}
5858
this.driver = options.driver;
5959
this.engine = options.engine;
60-
this.organizationId = options.organizationId;
61-
this.environmentId = options.environmentId;
60+
this.scope = {
61+
organizationId: options.organizationId,
62+
environmentId: options.environmentId,
63+
};
6264
}
6365

6466
/**

0 commit comments

Comments
 (0)