Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface ConnectionAndAppSetting {
connectionData: ServiceProviderConnectionModel | FunctionConnectionModel;
settings: Record<string, string>;
pathLocation: string[];
isUpdate?: boolean;
}

export interface ConnectionsData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class WorkflowUtility {
}

export function addConnectionInJson(connectionAndSetting: ConnectionAndAppSetting, connectionsJson: ConnectionsData): void {
const { connectionData, connectionKey, pathLocation } = connectionAndSetting;
const { connectionData, connectionKey, pathLocation, isUpdate } = connectionAndSetting;

let pathToSetConnectionsData: any = connectionsJson;

Expand All @@ -94,7 +94,7 @@ export function addConnectionInJson(connectionAndSetting: ConnectionAndAppSettin
pathToSetConnectionsData = pathToSetConnectionsData[path];
}

if (pathToSetConnectionsData && pathToSetConnectionsData[connectionKey]) {
if (pathToSetConnectionsData && pathToSetConnectionsData[connectionKey] && !isUpdate) {
// TODO: To show this in a notification of info bar on the blade.
// const message = 'ConnectionKeyAlreadyExist - Connection key \'{0}\' already exists.'.format(connectionKey);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,124 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
const mockUpdateConnectionReferenceWithMSIInLocalSettings = vi.fn();
const mockWriteLocalSettingsFile = vi.fn();
const mockGetLocalSettingsJson = vi.fn();
const mockPathExists = vi.fn();
const mockPathExistsSync = vi.fn();
const mockReadFile = vi.fn();
const mockWriteFormattedJson = vi.fn();
const mockIsCSharpProject = vi.fn();
const mockAddNewFileInCSharpProject = vi.fn();
const mockShouldParameterizeConnections = vi.fn();

// Mock dependencies
vi.mock('../../../appSettings/localSettings', () => ({
getLocalSettingsJson: mockGetLocalSettingsJson,
writeLocalSettingsFile: mockWriteLocalSettingsFile,
}));

vi.mock('fs-extra', () => ({
pathExists: mockPathExists,
pathExistsSync: mockPathExistsSync,
readFile: mockReadFile,
}));

vi.mock('../../fs', () => ({
writeFormattedJson: mockWriteFormattedJson,
}));

vi.mock('../../detectProjectLanguage', () => ({
isCSharpProject: mockIsCSharpProject,
}));

vi.mock('../updateBuildFile', () => ({
addNewFileInCSharpProject: mockAddNewFileInCSharpProject,
}));

vi.mock('../../vsCodeConfig/settings', () => ({
shouldParameterizeConnections: mockShouldParameterizeConnections,
}));

// Import the module after mocks are set up
const mockModule = await import('../connection');

describe('addConnectionDataInJson', () => {
const context = {} as any;
const projectPath = 'C:\\test\\project';
const existingConnection = {
displayName: 'Old knowledge hub',
cosmosDB: {
endpoint: 'https://old.documents.azure.com',
authentication: { type: 'ManagedServiceIdentity' },
},
};

beforeEach(() => {
vi.clearAllMocks();
mockPathExists.mockResolvedValue(true);
mockPathExistsSync.mockReturnValue(true);
mockReadFile.mockResolvedValue(
Buffer.from(
JSON.stringify({
knowledgeHubConnections: {
HubConnection: existingConnection,
},
})
)
);
mockShouldParameterizeConnections.mockReturnValue(false);
});

it('preserves duplicate connections for create operations', async () => {
const connectionData = { displayName: 'New knowledge hub' };

await mockModule.addConnectionDataInJson(
context,
projectPath,
{
connectionKey: 'HubConnection',
connectionData,
settings: {},
pathLocation: ['knowledgeHubConnections'],
},
{}
);

expect(mockWriteFormattedJson).not.toHaveBeenCalled();
});

it('overwrites an existing connection for update operations', async () => {
const connectionData = {
displayName: 'Updated knowledge hub',
cosmosDB: {
endpoint: 'https://new.documents.azure.com',
resourceId: '/subscriptions/sub/resourceGroups/rg/providers/Microsoft.DocumentDB/databaseAccounts/new',
authentication: { type: 'ManagedServiceIdentity' },
},
};

await mockModule.addConnectionDataInJson(
context,
projectPath,
{
connectionKey: 'HubConnection',
connectionData,
settings: {},
pathLocation: ['knowledgeHubConnections'],
isUpdate: true,
},
{}
);

expect(mockWriteFormattedJson).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
knowledgeHubConnections: {
HubConnection: connectionData,
},
})
);
});
});

describe('updateConnectionReferencesLocalMSI - parallel processing', () => {
const testProjectPath = '/test/project';
const testConnectionReferences = {
Expand Down
21 changes: 12 additions & 9 deletions apps/vs-code-designer/src/app/utils/codeless/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export async function getLogicAppProjectRoot(context: IActionContext, workflowFi
return projectRoot;
}

async function addConnectionDataInJson(
export async function addConnectionDataInJson(
context: IActionContext,
functionAppPath: string,
connectionAndAppSetting: ConnectionAndAppSetting<any>,
Expand All @@ -123,7 +123,7 @@ async function addConnectionDataInJson(
const connectionsJsonString = await getConnectionsJson(functionAppPath);
const connectionsJson = connectionsJsonString === '' ? {} : JSON.parse(connectionsJsonString);

const { connectionData, connectionKey, pathLocation, settings } = connectionAndAppSetting;
const { connectionData, connectionKey, pathLocation, settings, isUpdate } = connectionAndAppSetting;

let pathToSetConnectionsData = connectionsJson;

Expand All @@ -135,7 +135,7 @@ async function addConnectionDataInJson(
pathToSetConnectionsData = pathToSetConnectionsData[path];
}

if (pathToSetConnectionsData && pathToSetConnectionsData[connectionKey]) {
if (pathToSetConnectionsData && pathToSetConnectionsData[connectionKey] && !isUpdate) {
const message: string = localize('ConnectionKeyAlreadyExist', "Connection key '{0}' already exists.", connectionKey);
await vscode.window.showErrorMessage(message, localize('OK', 'OK'));
return;
Expand Down Expand Up @@ -444,7 +444,11 @@ export async function getCustomCodeToUpdate(
return { customCodeFiles: filteredCustomCodeMapping, appFiles };
}

export async function saveCustomCodeStandard(context: IActionContext, workflowFilePath: string, allCustomCodeFiles?: AllCustomCodeFiles): Promise<void> {
export async function saveCustomCodeStandard(
context: IActionContext,
workflowFilePath: string,
allCustomCodeFiles?: AllCustomCodeFiles
): Promise<void> {
const { customCodeFiles: customCode, appFiles } = allCustomCodeFiles ?? {};
if (!customCode || Object.keys(customCode).length === 0) {
return;
Expand All @@ -456,15 +460,14 @@ export async function saveCustomCodeStandard(context: IActionContext, workflowFi
const { isModified, isDeleted, fileData } = customCodeData;
if (isDeleted) {
return deleteCustomCode(workflowFolderPath, fileName);
} else if (isModified && fileData) {
}
if (isModified && fileData) {
return uploadCustomCode(workflowFolderPath, fileName, fileData);
}
return Promise.resolve();
});
// upload the app files needed for powershell actions
const appFilePromises = Object.entries(appFiles ?? {}).map(([fileName, fileData]) =>
uploadCustomCode(projectPath, fileName, fileData)
);
const appFilePromises = Object.entries(appFiles ?? {}).map(([fileName, fileData]) => uploadCustomCode(projectPath, fileName, fileData));
await Promise.all([...customCodePromises, ...appFilePromises]);
} catch (error) {
const errorMessage = `Failed to save custom code: ${error}`;
Expand Down Expand Up @@ -991,7 +994,7 @@ async function isMISettingEnabled(context: IActionContext, projectPath: string):
try {
const localSettings = await getLocalSettingsJson(context, projectPath);
const authMethod = localSettings.Values?.[workflowAuthenticationMethodKey];
return authMethod?.toLowerCase() === workflowAuthenticationMethodMIValue.toLowerCase()
return authMethod?.toLowerCase() === workflowAuthenticationMethodMIValue.toLowerCase();
} catch {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions apps/vs-code-react/src/app/designer/servicesHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,15 @@ const addConnectionInJson = (
connectionAndSetting: ConnectionAndAppSetting<LocalConnectionModel>,
connectionsJson: ConnectionsData
): ConnectionsData => {
const { connectionData, connectionKey, pathLocation } = connectionAndSetting;
const { connectionData, connectionKey, pathLocation, isUpdate } = connectionAndSetting;
const pathToSetConnectionsData: any = clone(connectionsJson);

for (const path of pathLocation) {
if (!pathToSetConnectionsData[path]) {
pathToSetConnectionsData[path] = {};
}

if (pathToSetConnectionsData && pathToSetConnectionsData[path][connectionKey]) {
if (pathToSetConnectionsData && pathToSetConnectionsData[path][connectionKey] && !isUpdate) {
break;
}
pathToSetConnectionsData[path][connectionKey] = connectionData;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ describe('knowledge connection utils', () => {
expect(result.values[0].parameters).toHaveProperty('cosmosDBKey');
expect(result.values[0].parameters).toHaveProperty('cosmosDbServiceAccountId');
expect(result.values[0].parameters).toHaveProperty('cosmosDBEndpoint');
expect(result.values[0].parameters.cosmosDbServiceAccountId.uiDefinition.constraints.serializationPath).toEqual([
'cosmosDB',
'resourceId',
]);
});

it('excludes cosmosDBKey parameter from ManagedServiceIdentity authentication', () => {
Expand Down Expand Up @@ -179,9 +183,18 @@ describe('knowledge connection utils', () => {
expect(connector).toEqual({ id: '/dummy/knowledgehub' });
expect(connectionInfo.displayName).toBe('My Connection');
expect(connectionInfo.connectionParameters).toBe(parameterValues);
expect(connectionInfo.isUpdate).toBe(false);
expect(options.connectionMetadata).toEqual({ required: true, type: 'KnowledgeHub' });
});

it('marks an edited knowledge connection as an update', async () => {
mockCreateConnection.mockResolvedValue({ id: '/connections/knowledgeHub' });

await createOrUpdateConnection({ displayName: 'My Connection' }, false);

expect(mockCreateConnection.mock.calls[0][2].isUpdate).toBe(true);
});

it('updates query cache after successful connection creation', async () => {
const createdConnection = { id: '/connections/knowledgeHub' };
mockCreateConnection.mockResolvedValue(createdConnection);
Expand Down Expand Up @@ -251,8 +264,7 @@ describe('knowledge connection utils', () => {
it('returns connection parameters excluding non-serializable ones', () => {
const result = getConnectionParametersForEdit(intl, undefined);

// cosmosDbServiceAccountId and cognitiveServiceAccountId have serialize: false
expect(result.connectionParameters).not.toHaveProperty('cosmosDbServiceAccountId');
expect(result.connectionParameters).toHaveProperty('cosmosDbServiceAccountId');
expect(result.connectionParameters).not.toHaveProperty('cognitiveServiceAccountId');

// These should be present as they are serializable
Expand All @@ -272,6 +284,7 @@ describe('knowledge connection utils', () => {
value: {
cosmosDB: {
endpoint: 'https://cosmos.test.com',
resourceId: '/subscriptions/1/resourceGroups/rg/providers/Microsoft.DocumentDB/databaseAccounts/db',
authentication: {
type: 'Key',
key: 'cosmos-secret-key',
Expand Down Expand Up @@ -300,6 +313,9 @@ describe('knowledge connection utils', () => {
const result = getConnectionParametersForEdit(intl, connection);

expect(result.parameterValues.cosmosDBEndpoint).toBe('https://cosmos.test.com');
expect(result.parameterValues.cosmosDbServiceAccountId).toBe(
'/subscriptions/1/resourceGroups/rg/providers/Microsoft.DocumentDB/databaseAccounts/db'
);
expect(result.parameterValues.cosmosDBKey).toBe('cosmos-secret-key');
expect(result.parameterValues.cosmosDBAuthenticationType).toBe('Key');
expect(result.parameterValues.openAIEndpoint).toBe('https://openai.test.com');
Expand Down
4 changes: 2 additions & 2 deletions libs/designer/src/lib/core/knowledge/utils/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const getAllConnectionParameters = (intl: IntlShape) => {
constraints: {
clearText: true,
required: 'true',
serialize: false,
serializationPath: ['cosmosDB', 'resourceId'],
Comment thread
bjbennet marked this conversation as resolved.
},
},
} as ConnectionParameter,
Expand Down Expand Up @@ -374,7 +374,7 @@ export const createOrUpdateConnection = async (parameterValues: Record<string, a
const connection = await ConnectionService().createConnection(
'HubConnection',
{ id: '/dummy/knowledgehub' } as unknown as Connector,
{ displayName, connectionParameters: parameterValues },
{ displayName, connectionParameters: parameterValues, isUpdate: !isCreate },
{ connectionParameters, connectionMetadata: { required: true, type: ConnectionType.KnowledgeHub } }
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ConnectionCreationInfo {
operationParameterValues?: Record<string, any>;
alternativeParameterValues?: Record<string, any>;
displayName?: string;
isUpdate?: boolean;
features?: ConnectionFeatureType;
parameterName?: string;
appSettings?: Record<string, string>;
Expand Down
Loading
Loading