@forge/sql

4.0.5-next.14.0.5-next.1-experimental-e9e08bb
out/__test__/sql.test.js
~out/__test__/sql.test.jsModified
+54
Index: package/out/__test__/sql.test.js
===================================================================
--- package/out/__test__/sql.test.js
+++ package/out/__test__/sql.test.js
@@ -8,8 +8,11 @@
 describe('SqlClient', () => {
     let sqlClient;
     let mockFetch;
     beforeEach(() => {
+        for (const key of Object.keys(process.env).filter((key) => key.startsWith('FORGE_LOCAL_')))
+            delete process.env[key];
+        delete global.__forge_local_fetch__;
         sqlClient = new sql_1.SqlClient();
         mockFetch = jest.fn();
         api_1.__fetchProduct.mockReturnValue(mockFetch);
         jest.clearAllMocks();
@@ -106,8 +109,59 @@
             const path = '/api/v1/execute';
             await expect(sqlClient['sendRequest'](path)).rejects.toThrow(mockError);
             expect(mockFetch).toHaveBeenCalledWith(path, expect.any(Object));
         });
+        it('routes local-storage requests directly to loopback without calling hosted Forge SQL', async () => {
+            Object.assign(process.env, {
+                FORGE_LOCAL_STORAGE: '1',
+                FORGE_LOCAL_SQL_URL: 'http://127.0.0.1:18091',
+                FORGE_LOCAL_STORAGE_APP_ID: 'local-app',
+                FORGE_LOCAL_STORAGE_ENVIRONMENT_ID: 'local-environment',
+                FORGE_LOCAL_STORAGE_INSTALLATION_ID: 'local-installation'
+            });
+            const localFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({ rows: [] }), { status: 200 }));
+            global.__forge_local_fetch__ = localFetch;
+            await sqlClient['sendRequest']('/api/v1/execute', { method: 'POST' });
+            expect(localFetch).toHaveBeenCalledWith(new URL('http://127.0.0.1:18091/api/v1/execute'), expect.objectContaining({
+                method: 'POST',
+                headers: expect.objectContaining({
+                    'x-forge-app-id': 'local-app',
+                    'x-forge-environment-id': 'local-environment',
+                    'x-forge-installation-id': 'local-installation',
+                    'x-atlassian-forgeapp-app-id': 'local-app',
+                    'x-atlassian-forgeapp-environment-id': 'local-environment',
+                    'x-atlassian-forgeapp-installation-id': 'local-installation'
+                })
+            }));
+            expect(api_1.__fetchProduct).not.toHaveBeenCalled();
+            delete global.__forge_local_fetch__;
+        });
+        it('fails closed for incomplete or non-loopback local SQL routing', async () => {
+            process.env.FORGE_LOCAL_STORAGE = '1';
+            process.env.FORGE_LOCAL_SQL_URL = 'http://127.0.0.1:18091';
+            await expect(sqlClient['sendRequest']('/api/v1/execute')).rejects.toThrow('local namespace is incomplete');
+            expect(api_1.__fetchProduct).not.toHaveBeenCalled();
+            Object.assign(process.env, {
+                FORGE_LOCAL_STORAGE_APP_ID: 'local-app',
+                FORGE_LOCAL_STORAGE_ENVIRONMENT_ID: 'local-environment',
+                FORGE_LOCAL_STORAGE_INSTALLATION_ID: 'local-installation',
+                FORGE_LOCAL_SQL_URL: 'https://example.com'
+            });
+            await expect(sqlClient['sendRequest']('/api/v1/execute')).rejects.toThrow('must be http://127.0.0.1:18091');
+            expect(api_1.__fetchProduct).not.toHaveBeenCalled();
+        });
+        it('fails closed when local SQL routing has no tunnel local-fetch bridge', async () => {
+            Object.assign(process.env, {
+                FORGE_LOCAL_STORAGE: '1',
+                FORGE_LOCAL_SQL_URL: 'http://127.0.0.1:18091',
+                FORGE_LOCAL_STORAGE_APP_ID: 'local-app',
+                FORGE_LOCAL_STORAGE_ENVIRONMENT_ID: 'local-environment',
+                FORGE_LOCAL_STORAGE_INSTALLATION_ID: 'local-installation'
+            });
+            delete global.__forge_local_fetch__;
+            await expect(sqlClient['sendRequest']('/api/v1/execute')).rejects.toThrow('requires the tunnel local-fetch bridge');
+            expect(api_1.__fetchProduct).not.toHaveBeenCalled();
+        });
     });
     describe('prepare', () => {
         it('should return a SqlStatement instance with query', () => {
             const statement = sqlClient.prepare('INSERT INTO test VALUES (?, ?)');