Skip to content
Merged
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 @@ -46,10 +46,6 @@ vi.mock('fs-extra', () => ({
pathExists: vi.fn(),
readFile: vi.fn(),
readdir: vi.fn(),
lstat: vi.fn(),
readlink: vi.fn(),
symlink: vi.fn(),
remove: vi.fn(),
}));

vi.mock('../../utils/appSettings/localSettings', async (importActual) => {
Expand Down Expand Up @@ -93,10 +89,6 @@ const mockedFse = fse as unknown as {
pathExists: ReturnType<typeof vi.fn>;
readFile: ReturnType<typeof vi.fn>;
readdir: ReturnType<typeof vi.fn>;
lstat: ReturnType<typeof vi.fn>;
readlink: ReturnType<typeof vi.fn>;
symlink: ReturnType<typeof vi.fn>;
remove: ReturnType<typeof vi.fn>;
};
const mockedAddOrUpdate = localSettings.addOrUpdateLocalAppSettings as unknown as ReturnType<typeof vi.fn>;
const mockedGetLocalSettingsJson = localSettings.getLocalSettingsJson as unknown as ReturnType<typeof vi.fn>;
Expand Down Expand Up @@ -898,115 +890,6 @@ describe('projectFilesConsistency', () => {
});
});
});

describe('artifacts junction', () => {
const designTimeDir = `${projectPath}/workflow-designtime`;
const artifactsTarget = `${projectPath}/Artifacts`;
const artifactsLink = `${designTimeDir}/Artifacts`;

beforeEach(() => {
// Default: valid design-time directory so host/settings are not rewritten
const hostPath = `${designTimeDir}/host.json`;
const settingsPath = `${designTimeDir}/local.settings.json`;
const validHost = JSON.stringify({
version: '2.0',
extensionBundle: { id: 'Microsoft.Azure.Functions.ExtensionBundle.Workflows', version: '[1.*, 2.0.0)' },
extensions: { workflow: { settings: { [workflowOperationDiscoveryHostModeKey]: 'true' } } },
});
const validSettings = JSON.stringify({
Values: {
APP_KIND: 'workflowapp',
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
FUNCTIONS_INPROC_NET8_ENABLED: '1',
ProjectDirectoryPath: projectPath,
},
});
mockFiles({ [designTimeDir]: '', [hostPath]: validHost, [settingsPath]: validSettings, [artifactsTarget]: '' });
mockedFse.lstat.mockRejectedValue(new Error('ENOENT'));
mockedFse.symlink.mockResolvedValue(undefined);
mockedFse.readlink.mockResolvedValue(artifactsTarget);
mockedFse.remove.mockResolvedValue(undefined);
});

it('creates a junction when Artifacts exists and no link is present', async () => {
mockedFse.lstat.mockRejectedValue(new Error('ENOENT'));

await ensureDesignTimeFiles(context, projectPath);

expect(mockedFse.symlink).toHaveBeenCalledWith(
expect.stringContaining('Artifacts'),
expect.stringContaining('workflow-designtime'),
'junction'
);
});

it('does not create a junction when Artifacts folder does not exist', async () => {
// Remove Artifacts from the mock file system
mockFiles({
[designTimeDir]: '',
[`${designTimeDir}/host.json`]: JSON.stringify({
version: '2.0',
extensionBundle: { id: 'Microsoft.Azure.Functions.ExtensionBundle.Workflows', version: '[1.*, 2.0.0)' },
extensions: { workflow: { settings: { [workflowOperationDiscoveryHostModeKey]: 'true' } } },
}),
[`${designTimeDir}/local.settings.json`]: JSON.stringify({
Values: {
APP_KIND: 'workflowapp',
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
FUNCTIONS_INPROC_NET8_ENABLED: '1',
ProjectDirectoryPath: projectPath,
},
}),
});

await ensureDesignTimeFiles(context, projectPath);

expect(mockedFse.symlink).not.toHaveBeenCalled();
});

it('does not recreate junction when it already points to correct target', async () => {
mockedFse.lstat.mockResolvedValue({ isSymbolicLink: () => true });
mockedFse.readlink.mockResolvedValue(artifactsTarget);

await ensureDesignTimeFiles(context, projectPath);

expect(mockedFse.symlink).not.toHaveBeenCalled();
expect(mockedFse.remove).not.toHaveBeenCalled();
});

it('recreates junction when it points to wrong target', async () => {
mockedFse.lstat.mockResolvedValue({ isSymbolicLink: () => true });
mockedFse.readlink.mockResolvedValue('/some/other/path');

await ensureDesignTimeFiles(context, projectPath);

expect(mockedFse.remove).toHaveBeenCalled();
expect(mockedFse.symlink).toHaveBeenCalledWith(
expect.stringContaining('Artifacts'),
expect.stringContaining('workflow-designtime'),
'junction'
);
});

it('does not overwrite a real directory at the link path', async () => {
mockedFse.lstat.mockResolvedValue({ isSymbolicLink: () => false });

await ensureDesignTimeFiles(context, projectPath);

expect(mockedFse.symlink).not.toHaveBeenCalled();
expect(mockedFse.remove).not.toHaveBeenCalled();
});

it('logs a warning when symlink creation fails', async () => {
mockedFse.lstat.mockRejectedValue(new Error('ENOENT'));
mockedFse.symlink.mockRejectedValue(new Error('EPERM'));

await ensureDesignTimeFiles(context, projectPath);

const lines = loggedLines();
expect(lines.some((l) => l.includes('junction'))).toBe(true);
});
});
});

describe('ensureHostFile', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import {
ProjectDirectoryPathKey,
appKindSetting,
artifactsDirectory,
connectionsFileName,
designTimeDirectoryName,
extensionBundleId,
Expand Down Expand Up @@ -270,10 +269,6 @@ export async function ensureDesignTimeFiles(
changedArtifacts.push(`${designTimeArtifactPrefix}${localSettingsFileName}`);
}

const artifactsTarget = path.join(projectPath, artifactsDirectory);
const artifactsLink = path.join(designTimeDirectory.fsPath, artifactsDirectory);
await ensureArtifactsSymlink(artifactsLink, artifactsTarget);

return { changedArtifacts };
Comment thread
andrew-eldridge marked this conversation as resolved.
}

Expand Down Expand Up @@ -427,25 +422,6 @@ async function readFileTextSafe(filePath: string): Promise<string> {
return '';
}

async function ensureArtifactsSymlink(artifactsLink: string, artifactsTarget: string): Promise<void> {
if (await fse.pathExists(artifactsTarget)) {
try {
const linkStat = await fse.lstat(artifactsLink).catch(() => null);
if (linkStat && linkStat.isSymbolicLink()) {
const currentTarget = await fse.readlink(artifactsLink);
if (!arePathsEqual(currentTarget, artifactsTarget)) {
await fse.remove(artifactsLink);
await fse.symlink(artifactsTarget, artifactsLink, 'junction');
}
} else if (!linkStat) {
await fse.symlink(artifactsTarget, artifactsLink, 'junction');
}
} catch {
ext.outputChannel.appendLog(localize('artifactsJunctionFailed', 'Failed to create Artifacts junction in design-time directory.'));
}
}
}

function arePathsEqual(path1?: string, path2?: string): boolean {
if (typeof path1 !== 'string' || typeof path2 !== 'string' || !path1 || !path2) {
return false;
Expand Down
Loading