Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ export function createClientFromRequest(request: Request): Base44Client {
const serverUrlHeader = request.headers.get("Base44-Api-Url");
const functionsVersion = request.headers.get("Base44-Functions-Version");
const stateHeader = request.headers.get("Base44-State");
const dataEnvHeader = request.headers.get("X-Data-Env");

if (!appId) {
throw new Error(
Expand Down Expand Up @@ -439,6 +440,14 @@ export function createClientFromRequest(request: Request): Base44Client {
if (stateHeader) {
additionalHeaders["Base44-State"] = stateHeader;
}
// Propagate the data environment so entity operations from the function stay
// in the same environment (e.g. test data) as the triggering request. This
// matters for the user-scoped client: unlike the service token, the user JWT
// carries no data-env, so without forwarding this header the callbacks fall
// back to production data even when the app runs in test-data mode.
if (dataEnvHeader) {
Comment thread
eliorh marked this conversation as resolved.
Outdated
additionalHeaders["X-Data-Env"] = dataEnvHeader;
}

return createClient({
serverUrl: serverUrlHeader || "https://base44.app",
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,61 @@ describe('Service Role Authorization Headers', () => {
expect(scope.isDone()).toBe(true);
});

test('should propagate X-Data-Env header on user-scoped API requests when created from request', async () => {
const mockRequest = {
headers: {
get: (name) => {
const headers = {
'Authorization': 'Bearer user-token-123',
'Base44-App-Id': appId,
'Base44-Api-Url': serverUrl,
'X-Data-Env': 'dev'
};
return headers[name] || null;
}
}
};

const client = createClientFromRequest(mockRequest);

// The user-scoped client (not asServiceRole) must still carry the data env
// so test-mode function callbacks hit test data, not production.
scope.get(`/api/apps/${appId}/entities/Todo`)
.matchHeader('X-Data-Env', 'dev')
.matchHeader('Authorization', 'Bearer user-token-123')
.reply(200, { items: [], total: 0 });

await client.entities.Todo.list();

expect(scope.isDone()).toBe(true);
});

test('should not include X-Data-Env header when not present in original request', async () => {
const mockRequest = {
headers: {
get: (name) => {
const headers = {
'Authorization': 'Bearer user-token-123',
'Base44-App-Id': appId,
'Base44-Api-Url': serverUrl
};
return headers[name] || null;
}
}
};

const client = createClientFromRequest(mockRequest);

scope.get(`/api/apps/${appId}/entities/Todo`)
.matchHeader('X-Data-Env', (val) => !val) // Should not have this header
.matchHeader('Authorization', 'Bearer user-token-123')
.reply(200, { items: [], total: 0 });

await client.entities.Todo.list();

expect(scope.isDone()).toBe(true);
});

test('should not include Base44-State header when not present in original request', async () => {
const mockRequest = {
headers: {
Expand Down
Loading