Skip to content

Commit 14242d6

Browse files
Vandita2020Vandita Patidar
andauthored
fix(lambda): checking if folder exists (#6899)
## Problem In the existing Serverless Land project, we need to check if a folder with the same name already exists before downloading the code. This could help avoid potential issues that might arise from overwriting existing files or folders. ## Solution The code checks if a folder with the same name already exists, and prompts the user to choose whether to override it or not. This allows the user to decide how to handle potential conflicts with existing files or folders. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Vandita Patidar <[email protected]>
1 parent 7cacad2 commit 14242d6

File tree

3 files changed

+100
-45
lines changed

3 files changed

+100
-45
lines changed

packages/core/src/awsService/appBuilder/serverlessLand/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ExtContext } from '../../../shared/extensions'
1515
import { addFolderToWorkspace } from '../../../shared/utilities/workspaceUtils'
1616
import { ToolkitError } from '../../../shared/errors'
1717
import { fs } from '../../../shared/fs/fs'
18+
import { handleOverwriteConflict } from '../../../shared/utilities/messages'
1819
import { getPattern } from '../../../shared/utilities/downloadPatterns'
1920
import { MetadataManager } from './metadataManager'
2021

@@ -89,6 +90,9 @@ export async function launchProjectCreationWizard(
8990
export async function downloadPatternCode(config: CreateServerlessLandWizardForm, assetName: string): Promise<void> {
9091
const fullAssetName = assetName + '.zip'
9192
const location = vscode.Uri.joinPath(config.location, config.name)
93+
94+
await handleOverwriteConflict(location)
95+
9296
try {
9397
await getPattern(serverlessLandOwner, serverlessLandRepo, fullAssetName, location, true)
9498
} catch (error) {

packages/core/src/shared/utilities/messages.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as vscode from 'vscode'
77
import * as nls from 'vscode-nls'
8+
import * as path from 'path'
89
import * as localizedText from '../localizedText'
910
import { getLogger } from '../../shared/logger/logger'
1011
import { ProgressEntry } from '../../shared/vscode/window'
@@ -14,6 +15,8 @@ import { Timeout } from './timeoutUtils'
1415
import { addCodiconToString } from './textUtilities'
1516
import { getIcon, codicon } from '../icons'
1617
import globals from '../extensionGlobals'
18+
import { ToolkitError } from '../../shared/errors'
19+
import { fs } from '../../shared/fs/fs'
1720
import { openUrl } from './vsCodeUtils'
1821
import { AmazonQPromptSettings, ToolkitPromptSettings } from '../../shared/settings'
1922
import { telemetry, ToolkitShowNotification } from '../telemetry/telemetry'
@@ -140,6 +143,41 @@ export async function showViewLogsMessage(
140143
})
141144
}
142145

146+
/**
147+
* Checks if a path exists and prompts user for overwrite confirmation if it does.
148+
* @param path The file or directory path to check
149+
* @param itemName The name of the item for display in the message
150+
* @returns Promise<boolean> - true if should proceed (path doesn't exist or user confirmed overwrite)
151+
*/
152+
export async function handleOverwriteConflict(location: vscode.Uri): Promise<boolean> {
153+
if (!(await fs.exists(location))) {
154+
return true
155+
}
156+
157+
const choice = showConfirmationMessage({
158+
prompt: localize(
159+
'AWS.toolkit.confirmOverwrite',
160+
'{0} already exists in the selected directory, overwrite?',
161+
location.fsPath
162+
),
163+
confirm: localize('AWS.generic.overwrite', 'Yes'),
164+
cancel: localize('AWS.generic.cancel', 'No'),
165+
type: 'warning',
166+
})
167+
168+
if (!choice) {
169+
throw new ToolkitError(`Folder already exists: ${path.basename(location.fsPath)}`)
170+
}
171+
172+
try {
173+
await fs.delete(location, { recursive: true, force: true })
174+
} catch (error) {
175+
throw ToolkitError.chain(error, `Failed to delete existing folder: ${path.basename(location.fsPath)}`)
176+
}
177+
178+
return true
179+
}
180+
143181
/**
144182
* Shows a modal confirmation (warning) message with buttons to confirm or cancel.
145183
*

packages/core/src/test/awsService/appBuilder/serverlessLand/main.test.ts

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { fs } from '../../../../shared/fs/fs'
2323
import * as downloadPatterns from '../../../../shared/utilities/downloadPatterns'
2424
import { ExtContext } from '../../../../shared/extensions'
2525
import { workspaceUtils } from '../../../../shared'
26+
import * as messages from '../../../../shared/utilities/messages'
2627
import * as downloadPattern from '../../../../shared/utilities/downloadPatterns'
2728
import * as wizardModule from '../../../../awsService/appBuilder/serverlessLand/wizard'
2829

@@ -80,63 +81,75 @@ describe('createNewServerlessLandProject', () => {
8081
})
8182
})
8283

84+
function assertDownloadPatternCall(getPatternStub: sinon.SinonStub, mockConfig: any) {
85+
const mockAssetName = 'test-project-sam-python.zip'
86+
const serverlessLandOwner = 'aws-samples'
87+
const serverlessLandRepo = 'serverless-patterns'
88+
const mockLocation = vscode.Uri.joinPath(mockConfig.location, mockConfig.name)
89+
90+
assert(getPatternStub.calledOnce)
91+
assert(getPatternStub.firstCall.args[0] === serverlessLandOwner)
92+
assert(getPatternStub.firstCall.args[1] === serverlessLandRepo)
93+
assert(getPatternStub.firstCall.args[2] === mockAssetName)
94+
assert(getPatternStub.firstCall.args[3].toString() === mockLocation.toString())
95+
assert(getPatternStub.firstCall.args[4] === true)
96+
}
97+
8398
describe('downloadPatternCode', () => {
8499
let sandbox: sinon.SinonSandbox
85100
let getPatternStub: sinon.SinonStub
101+
let mockConfig: any
86102

87103
beforeEach(function () {
88104
sandbox = sinon.createSandbox()
89105
getPatternStub = sandbox.stub(downloadPatterns, 'getPattern')
106+
mockConfig = {
107+
name: 'test-project',
108+
location: vscode.Uri.file('/test'),
109+
pattern: 'test-project-sam-python',
110+
runtime: 'python',
111+
iac: 'sam',
112+
assetName: 'test-project-sam-python',
113+
}
90114
})
91115
afterEach(function () {
92116
sandbox.restore()
117+
getPatternStub.restore()
93118
})
94-
const mockConfig = {
95-
name: 'test-project',
96-
location: vscode.Uri.file('/test'),
97-
pattern: 'test-project-sam-python',
98-
runtime: 'python',
99-
iac: 'sam',
100-
assetName: 'test-project-sam-python',
101-
}
102119
it('successfully downloads pattern code', async () => {
103-
const mockAssetName = 'test-project-sam-python.zip'
104-
const serverlessLandOwner = 'aws-samples'
105-
const serverlessLandRepo = 'serverless-patterns'
106-
const mockLocation = vscode.Uri.joinPath(mockConfig.location, mockConfig.name)
107-
108-
await downloadPatternCode(mockConfig, 'test-project-sam-python')
109-
assert(getPatternStub.calledOnce)
110-
assert(getPatternStub.firstCall.args[0] === serverlessLandOwner)
111-
assert(getPatternStub.firstCall.args[1] === serverlessLandRepo)
112-
assert(getPatternStub.firstCall.args[2] === mockAssetName)
113-
assert(getPatternStub.firstCall.args[3].toString() === mockLocation.toString())
114-
assert(getPatternStub.firstCall.args[4] === true)
115-
})
116-
it('handles download failure', async () => {
117-
const mockAssetName = 'test-project-sam-python.zip'
118-
const error = new Error('Download failed')
119-
getPatternStub.rejects(error)
120-
try {
121-
await downloadPatternCode(mockConfig, mockAssetName)
122-
assert.fail('Expected an error to be thrown')
123-
} catch (err: any) {
124-
assert.strictEqual(err.message, 'Failed to download pattern: Error: Download failed')
125-
}
120+
sandbox.stub(messages, 'handleOverwriteConflict').resolves(true)
121+
122+
await downloadPatternCode(mockConfig, mockConfig.assetName)
123+
assertDownloadPatternCall(getPatternStub, mockConfig)
124+
})
125+
it('downloads pattern when directory exists and user confirms overwrite', async function () {
126+
sandbox.stub(messages, 'handleOverwriteConflict').resolves(true)
127+
128+
await downloadPatternCode(mockConfig, mockConfig.assetName)
129+
assertDownloadPatternCall(getPatternStub, mockConfig)
130+
})
131+
it('throws error when directory exists and user cancels overwrite', async function () {
132+
const handleOverwriteStub = sandbox.stub(messages, 'handleOverwriteConflict')
133+
handleOverwriteStub.rejects(new Error('Folder already exists: test-project'))
134+
135+
await assert.rejects(
136+
() => downloadPatternCode(mockConfig, mockConfig.assetName),
137+
/Folder already exists: test-project/
138+
)
126139
})
127140
})
128141

129142
describe('openReadmeFile', () => {
130-
let sandbox: sinon.SinonSandbox
143+
let testsandbox: sinon.SinonSandbox
131144
let spyExecuteCommand: sinon.SinonSpy
132145

133146
beforeEach(function () {
134-
sandbox = sinon.createSandbox()
135-
spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand')
147+
testsandbox = sinon.createSandbox()
148+
spyExecuteCommand = testsandbox.spy(vscode.commands, 'executeCommand')
136149
})
137150

138151
afterEach(function () {
139-
sandbox.restore()
152+
testsandbox.restore()
140153
})
141154
const mockConfig = {
142155
name: 'test-project',
@@ -148,43 +161,43 @@ describe('openReadmeFile', () => {
148161
}
149162
it('successfully opens README file', async () => {
150163
const mockReadmeUri = vscode.Uri.file('/test/README.md')
151-
sandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
164+
testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
152165

153-
sandbox.stub(fs, 'exists').resolves(true)
166+
testsandbox.stub(fs, 'exists').resolves(true)
154167

155168
// When
156169
await openReadmeFile(mockConfig)
157170
// Then
158-
sandbox.assert.calledWith(spyExecuteCommand, 'workbench.action.focusFirstEditorGroup')
159-
sandbox.assert.calledWith(spyExecuteCommand, 'markdown.showPreview')
171+
testsandbox.assert.calledWith(spyExecuteCommand, 'workbench.action.focusFirstEditorGroup')
172+
testsandbox.assert.calledWith(spyExecuteCommand, 'markdown.showPreview')
160173
})
161174

162175
it('handles missing README file', async () => {
163176
const mockReadmeUri = vscode.Uri.file('/test/file.md')
164-
sandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
177+
testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
165178

166-
sandbox.stub(fs, 'exists').resolves(false)
179+
testsandbox.stub(fs, 'exists').resolves(false)
167180

168181
// When
169182
await openReadmeFile(mockConfig)
170183
// Then
171-
sandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
184+
testsandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
172185
assert.ok(true, 'Function should return without throwing error when README is not found')
173186
})
174187

175188
it('handles error with opening README file', async () => {
176189
const mockReadmeUri = vscode.Uri.file('/test/README.md')
177-
sandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
190+
testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri)
178191

179-
sandbox.stub(fs, 'exists').rejects(new Error('File system error'))
192+
testsandbox.stub(fs, 'exists').rejects(new Error('File system error'))
180193

181194
// When
182195
await assert.rejects(() => openReadmeFile(mockConfig), {
183196
name: 'Error',
184197
message: 'Error processing README file',
185198
})
186199
// Then
187-
sandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
200+
testsandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview')
188201
})
189202
})
190203

0 commit comments

Comments
 (0)