Skip to content

Commit cceadef

Browse files
ShadowCat567Vieltojarvi
andauthored
Update permissions tests in ProfileController to use mock (#2507)
* fix tests * changeset --------- Co-authored-by: Vieltojarvi <[email protected]>
1 parent 9c681fc commit cceadef

File tree

3 files changed

+108
-66
lines changed

3 files changed

+108
-66
lines changed

.changeset/sweet-sheep-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@aws-amplify/backend-cli': patch
3+
---
4+
5+
updated tests and constructor for profileController

packages/cli/src/commands/configure/profile_controller.test.ts

Lines changed: 93 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import path from 'node:path';
2-
import { after, before, beforeEach, describe, it } from 'node:test';
2+
import { after, before, beforeEach, describe, it, mock } from 'node:test';
33
import fs from 'fs/promises';
44
import { ProfileController } from './profile_controller.js';
55
import assert from 'node:assert';
66
import { loadSharedConfigFiles } from '@smithy/shared-ini-file-loader';
77
import { EOL } from 'node:os';
8-
import { chmodSync } from 'node:fs';
98
import { AmplifyUserError } from '@aws-amplify/platform-core';
109

1110
const testAccessKeyId = 'testAccessKeyId';
@@ -220,64 +219,6 @@ void describe('profile controller', () => {
220219
);
221220
});
222221

223-
void it('throws error if config file already exists and is missing read permissions', async () => {
224-
if (process.platform.startsWith('win')) {
225-
// Windows does not have the same behavior when files are missing permissions
226-
return;
227-
}
228-
229-
const expectedErr = new AmplifyUserError('PermissionsError', {
230-
message: `You do not have the permissions to read this file: ${configFilePath}.`,
231-
resolution: `Ensure that you have the right permissions to read from ${configFilePath}.`,
232-
});
233-
chmodSync(configFilePath, 0o000);
234-
await assert.rejects(
235-
async () =>
236-
await profileController.createOrAppendAWSFiles({
237-
profile: testProfile2,
238-
region: testRegion2,
239-
accessKeyId: testAccessKeyId2,
240-
secretAccessKey: testSecretAccessKey2,
241-
}),
242-
(error: AmplifyUserError) => {
243-
assert.strictEqual(error.name, expectedErr.name);
244-
assert.strictEqual(error.message, expectedErr.message);
245-
assert.strictEqual(error.resolution, expectedErr.resolution);
246-
return true;
247-
}
248-
);
249-
});
250-
251-
void it('throws error if config file already exists and is missing write permissions', async () => {
252-
if (process.platform.startsWith('win')) {
253-
// Windows does not have the same behavior when files are missing permissions
254-
return;
255-
}
256-
257-
const expectedErr = new AmplifyUserError('PermissionsError', {
258-
message: `You do not have the permissions to write to this file: ${configFilePath}`,
259-
resolution: `Ensure that you have the right permissions to write to ${configFilePath}.`,
260-
});
261-
262-
chmodSync(configFilePath, 0o444);
263-
264-
await assert.rejects(
265-
async () =>
266-
await profileController.createOrAppendAWSFiles({
267-
profile: testProfile2,
268-
region: testRegion2,
269-
accessKeyId: testAccessKeyId2,
270-
secretAccessKey: testSecretAccessKey2,
271-
}),
272-
(error: AmplifyUserError) => {
273-
assert.strictEqual(error.name, expectedErr.name);
274-
assert.strictEqual(error.message, expectedErr.message);
275-
assert.strictEqual(error.resolution, expectedErr.resolution);
276-
return true;
277-
}
278-
);
279-
});
280-
281222
void it('creates directory if does not exist', async () => {
282223
// delete directory
283224
await fs.rm(testDir, { recursive: true, force: true });
@@ -354,6 +295,98 @@ void describe('profile controller', () => {
354295
assert.equal(await profileController.profileExists(testProfile), true);
355296
});
356297
});
298+
299+
void describe('Missing permissions on config file', () => {
300+
let testDir: string;
301+
let configFilePath: string;
302+
let credFilePath: string;
303+
304+
const fsMock = {
305+
readFile: mock.fn<
306+
(path: string, encoding: BufferEncoding) => Promise<void | string>
307+
>(() => Promise.resolve()),
308+
appendFile: mock.fn<() => Promise<void>>(() => Promise.resolve()),
309+
};
310+
311+
const profileController = new ProfileController(
312+
fsMock as unknown as typeof fs
313+
);
314+
315+
before(async () => {
316+
testDir = await fs.mkdtemp('amplify_cmd_test');
317+
configFilePath = path.join(process.cwd(), testDir, 'config');
318+
credFilePath = path.join(process.cwd(), testDir, 'credentials');
319+
320+
process.env.AWS_CONFIG_FILE = configFilePath;
321+
process.env.AWS_SHARED_CREDENTIALS_FILE = credFilePath;
322+
323+
fsMock.readFile.mock.resetCalls();
324+
fsMock.appendFile.mock.resetCalls();
325+
});
326+
327+
after(async () => {
328+
await fs.rm(testDir, { recursive: true, force: true });
329+
delete process.env.AWS_CONFIG_FILE;
330+
delete process.env.AWS_SHARED_CREDENTIALS_FILE;
331+
});
332+
333+
void it('throws error if config file already exists and is missing read permissions', async () => {
334+
const expectedErr = new AmplifyUserError('PermissionsError', {
335+
message: `You do not have the permissions to read this file: ${configFilePath}.`,
336+
resolution: `Ensure that you have the right permissions to read from ${configFilePath}.`,
337+
});
338+
339+
fsMock.readFile.mock.mockImplementationOnce(() =>
340+
Promise.reject(new Error('EACCES: Permission denied'))
341+
);
342+
343+
await assert.rejects(
344+
async () =>
345+
profileController.createOrAppendAWSFiles({
346+
profile: testProfile2,
347+
region: testRegion2,
348+
accessKeyId: testAccessKeyId2,
349+
secretAccessKey: testSecretAccessKey2,
350+
}),
351+
(error: AmplifyUserError) => {
352+
assert.strictEqual(error.name, expectedErr.name);
353+
assert.strictEqual(error.message, expectedErr.message);
354+
assert.strictEqual(error.resolution, expectedErr.resolution);
355+
return true;
356+
}
357+
);
358+
});
359+
360+
void it('throws error if config file already exists and is missing write permissions', async () => {
361+
const expectedErr = new AmplifyUserError('PermissionsError', {
362+
message: `You do not have the permissions to write to this file: ${configFilePath}`,
363+
resolution: `Ensure that you have the right permissions to write to ${configFilePath}.`,
364+
});
365+
366+
fsMock.readFile.mock.mockImplementationOnce(
367+
(path: string, encoding: BufferEncoding) => fs.readFile(path, encoding)
368+
);
369+
fsMock.appendFile.mock.mockImplementationOnce(() =>
370+
Promise.reject(new Error('EACCES: Permission denied'))
371+
);
372+
373+
await assert.rejects(
374+
async () =>
375+
profileController.createOrAppendAWSFiles({
376+
profile: testProfile2,
377+
region: testRegion2,
378+
accessKeyId: testAccessKeyId2,
379+
secretAccessKey: testSecretAccessKey2,
380+
}),
381+
(error: AmplifyUserError) => {
382+
assert.strictEqual(error.name, expectedErr.name);
383+
assert.strictEqual(error.message, expectedErr.message);
384+
assert.strictEqual(error.resolution, expectedErr.resolution);
385+
return true;
386+
}
387+
);
388+
});
389+
});
357390
});
358391

359392
const assertFilePermissionsAreOwnerReadWriteOnly = async (filePath: string) => {

packages/cli/src/commands/configure/profile_controller.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '@smithy/shared-ini-file-loader';
66
import { fromIni } from '@aws-sdk/credential-providers';
77
import { EOL } from 'os';
8-
import fs from 'fs/promises';
8+
import _fs from 'fs/promises';
99
import path from 'path';
1010
import { existsSync } from 'fs';
1111
import { AmplifyUserError } from '@aws-amplify/platform-core';
@@ -36,6 +36,10 @@ export type ProfileOptions = ConfigProfileOptions & CredentialProfileOptions;
3636
* Manages AWS profiles.
3737
*/
3838
export class ProfileController {
39+
/**
40+
* constructor
41+
*/
42+
constructor(private readonly fs = _fs) {}
3943
/**
4044
* Return true if the provided profile exists in the aws config and/or credential file.
4145
*/
@@ -66,7 +70,7 @@ export class ProfileController {
6670

6771
const dirName = path.dirname(filePath);
6872
if (!existsSync(dirName)) {
69-
await fs.mkdir(dirName, { recursive: true });
73+
await this.fs.mkdir(dirName, { recursive: true });
7074
}
7175

7276
const fileEndsWithEOL = await this.isFileEndsWithEOL(filePath);
@@ -79,7 +83,7 @@ export class ProfileController {
7983
configData += `region = ${options.region}${EOL}`;
8084

8185
try {
82-
await fs.appendFile(filePath, configData, { mode: '600' });
86+
await this.fs.appendFile(filePath, configData, { mode: '600' });
8387
} catch (err) {
8488
const error = err as Error;
8589
if (error.message.includes('EACCES')) {
@@ -115,7 +119,7 @@ export class ProfileController {
115119

116120
const dirName = path.dirname(filePath);
117121
if (!existsSync(dirName)) {
118-
await fs.mkdir(dirName, { recursive: true });
122+
await this.fs.mkdir(dirName, { recursive: true });
119123
}
120124

121125
const fileEndsWithEOL = await this.isFileEndsWithEOL(filePath);
@@ -125,7 +129,7 @@ export class ProfileController {
125129
credentialData += `aws_access_key_id = ${options.accessKeyId}${EOL}`;
126130
credentialData += `aws_secret_access_key = ${options.secretAccessKey}${EOL}`;
127131

128-
await fs.appendFile(filePath, credentialData, { mode: '600' });
132+
await this.fs.appendFile(filePath, credentialData, { mode: '600' });
129133

130134
// validate after write. It is to ensure this function is compatible with the current AWS format.
131135
const provider = fromIni({
@@ -144,7 +148,7 @@ export class ProfileController {
144148

145149
private isFileEndsWithEOL = async (filePath: string): Promise<boolean> => {
146150
try {
147-
const data = await fs.readFile(filePath, 'utf-8');
151+
const data = await this.fs.readFile(filePath, 'utf-8');
148152
return data.length > 0 && data.slice(-EOL.length) === EOL;
149153
} catch (err) {
150154
const error = err as Error;

0 commit comments

Comments
 (0)