Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import type { CallbackInfo, ConnectionsData, Note, ParametersData, Workflow } fr
import { Artifact } from '../Models/Workflow';
import { validateResourceId } from '../Utilities/resourceUtilities';
import { convertDesignerWorkflowToConsumptionWorkflow } from './ConsumptionSerializationHelpers';
import { getHostConfig, getReactQueryClient, runsQueriesKeys, type AllCustomCodeFiles } from '@microsoft/logic-apps-designer';
import { getHostConfig, getReactQueryClient, runsQueriesKeys } from '@microsoft/logic-apps-designer';
import { CustomCodeService, LogEntryLevel, LoggerService, equals, getAppFileForFileExtension } from '@microsoft/logic-apps-shared';
import type { AgentQueryParams, AgentURL, LogicAppsV2, McpServer, VFSObject } from '@microsoft/logic-apps-shared';
import axios from 'axios';
import jwt_decode from 'jwt-decode';
import { useQuery } from '@tanstack/react-query';
import { isSuccessResponse } from './HttpClient';
import { fetchFileData, fetchFilesFromFolder } from './vfsService';
import type { CustomCodeFileNameMapping, ServerNotificationData } from '@microsoft/logic-apps-designer';
import type { CustomCodeFileNameMapping, ServerNotificationData, AllCustomCodeFiles } from '@microsoft/logic-apps-designer';
import { HybridAppUtility, hybridApiVersion } from '../Utilities/HybridAppUtilities';
import type { HostingPlanTypes } from '../../../state/workflowLoadingSlice';
import { ArmParser } from '../Utilities/ArmParser';
Expand Down Expand Up @@ -292,21 +292,24 @@ export const listCallbackUrl = async (
};

// Helper function to fetch A2A authentication key
const fetchA2AAuthKey = async (siteResourceId: string, workflowName: string, isDraftMode?: boolean) => {
export const fetchA2AAuthKey = async (siteResourceId: string, workflowName: string, isDraftMode?: boolean) => {
const currentDate: Date = new Date();
const data = {
expiry: new Date(currentDate.getTime() + 86400000).toISOString(),
keyType: 'Primary',
};
const authToken = {
Authorization: `Bearer ${environment.armToken}`,
};
const endpoint = `${baseUrl}${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/${isDraftMode ? 'listDraftApiKeys' : 'listApiKeys'}`;

const response = await axios.post(
`${baseUrl}${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/workflows/${workflowName}/${isDraftMode ? 'listDraftApiKeys' : 'listApiKeys'}?api-version=2018-11-01`,
{
expiry: new Date(currentDate.getTime() + 86400000).toISOString(),
keyType: 'Primary',
},
{
headers: {
Authorization: `Bearer ${environment.armToken}`,
},
}
);
if (HybridAppUtility.isHybridLogicApp(siteResourceId)) {
return HybridAppUtility.postProxy(endpoint, data, authToken);
}

const response = await axios.post(`${endpoint}?api-version=2018-11-01`, data, {
headers: authToken,
});
return response.data;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import axios from 'axios';
import { fetchA2AAuthKey } from '../WorkflowAndArtifacts';
import { HybridAppUtility } from '../../Utilities/HybridAppUtilities';

// Mock axios
vi.mock('axios');

// Mock the environment
vi.mock('../../../../../environments/environment', () => ({
environment: {
armToken: 'test-arm-token',
},
}));

describe('fetchA2AAuthKey', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-02-23T12:00:00.000Z'));
});

afterEach(() => {
vi.clearAllMocks();
vi.useRealTimers();
});

describe('when siteResourceId is a hybrid logic app', () => {
const hybridSiteResourceId = '/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.App/containerApps/myHybridApp';
const workflowName = 'myWorkflow';

it('should call HybridAppUtility.postProxy with correct parameters', async () => {
const mockResponse = { key: 'hybrid-api-key', endpoint: 'https://example.com' };
const postProxySpy = vi.spyOn(HybridAppUtility, 'postProxy').mockResolvedValue(mockResponse);

const result = await fetchA2AAuthKey(hybridSiteResourceId, workflowName);

expect(postProxySpy).toHaveBeenCalledWith(
'https://management.azure.com/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.App/containerApps/myHybridApp/hostruntime/runtime/webhooks/workflow/api/management/workflows/myWorkflow/listApiKeys',
{
expiry: '2026-02-24T12:00:00.000Z', // 24 hours later
keyType: 'Primary',
},
{
Authorization: 'Bearer test-arm-token',
}
);
expect(result).toEqual(mockResponse);
});

it('should use listDraftApiKeys endpoint when isDraftMode is true', async () => {
const mockResponse = { key: 'draft-api-key' };
const postProxySpy = vi.spyOn(HybridAppUtility, 'postProxy').mockResolvedValue(mockResponse);

await fetchA2AAuthKey(hybridSiteResourceId, workflowName, true);

expect(postProxySpy).toHaveBeenCalledWith(expect.stringContaining('/listDraftApiKeys'), expect.any(Object), expect.any(Object));
});

it('should NOT call axios.post directly for hybrid apps', async () => {
vi.spyOn(HybridAppUtility, 'postProxy').mockResolvedValue({ key: 'test' });

await fetchA2AAuthKey(hybridSiteResourceId, workflowName);

expect(axios.post).not.toHaveBeenCalled();
});
});

describe('when siteResourceId is a standard logic app', () => {
const standardSiteResourceId = '/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Web/sites/myStandardApp';
const workflowName = 'myWorkflow';

it('should call axios.post with api-version query parameter', async () => {
const mockResponse = { data: { key: 'standard-api-key', endpoint: 'https://example.com' } };
(axios.post as any).mockResolvedValue(mockResponse);

const result = await fetchA2AAuthKey(standardSiteResourceId, workflowName);

expect(axios.post).toHaveBeenCalledWith(
'https://management.azure.com/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Web/sites/myStandardApp/hostruntime/runtime/webhooks/workflow/api/management/workflows/myWorkflow/listApiKeys?api-version=2018-11-01',
{
expiry: '2026-02-24T12:00:00.000Z',
keyType: 'Primary',
},
{
headers: {
Authorization: 'Bearer test-arm-token',
},
}
);
expect(result).toEqual(mockResponse.data);
});

it('should use listDraftApiKeys endpoint when isDraftMode is true', async () => {
const mockResponse = { data: { key: 'draft-api-key' } };
(axios.post as any).mockResolvedValue(mockResponse);

await fetchA2AAuthKey(standardSiteResourceId, workflowName, true);

expect(axios.post).toHaveBeenCalledWith(
expect.stringContaining('/listDraftApiKeys?api-version=2018-11-01'),
expect.any(Object),
expect.any(Object)
);
});

it('should NOT call HybridAppUtility.postProxy for standard apps', async () => {
const postProxySpy = vi.spyOn(HybridAppUtility, 'postProxy');
(axios.post as any).mockResolvedValue({ data: { key: 'test' } });

await fetchA2AAuthKey(standardSiteResourceId, workflowName);

expect(postProxySpy).not.toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,8 @@ export class HybridAppUtility {
}

public static async postProxy<T>(uri: string, data: any, headers: Record<string, string>, params?: Record<string, any>): Promise<T> {
const splitUri = uri.split('/hostruntime/');
const appName = splitUri[0].split('/').pop();
return (
await axios.post<T>(`${splitUri[0]}/providers/Microsoft.App/logicapps/${appName}/invoke?api-version=${hybridApiVersion}`, data, {
headers: {
...headers,
'x-ms-logicapps-proxy-path': `/${splitUri[1]}`,
'x-ms-logicapps-proxy-method': 'POST',
},
params,
})
).data;
const response = await HybridAppUtility.postProxyResponse<T>(uri, data, headers, params);
return response.data;
}

public static async postProxyResponse<T>(uri: string, data: any, headers: Record<string, string>, params?: Record<string, any>) {
Expand All @@ -48,7 +38,7 @@ export class HybridAppUtility {
return await axios.post<T>(`${baseUri}/providers/Microsoft.App/logicapps/${appName}/invoke?api-version=${hybridApiVersion}`, data, {
headers: {
...headers,
'x-ms-logicapps-proxy-path': `${path}/`,
'x-ms-logicapps-proxy-path': `/${path}`,
'x-ms-logicapps-proxy-method': 'POST',
},
params,
Expand Down
2 changes: 1 addition & 1 deletion vitest.workspace.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export default ['libs/'];
export default ['libs/', 'apps/Standalone/'];
Loading