Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pr/338 #339

Merged
merged 3 commits into from
Oct 25, 2023
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
168 changes: 168 additions & 0 deletions src/ops/cloud/SecretsOps.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* To record and update snapshots, you must perform 3 steps in order:
*
* 1. Record API responses
*
* To record API responses, you must call the test:record script and
* override all the connection state variables required to connect to the
* env to record from:
*
* ATTENTION: For the recording to succeed, you MUST make sure to use a
* user account, not a service account.
*
* FRODO_DEBUG=1 FRODO_HOST=frodo-dev npm run test:record SecretsOps
*
* The above command assumes that you have a connection profile for
* 'frodo-dev' on your development machine.
*
* 2. Update snapshots
*
* After recording API responses, you must manually update/create snapshots
* by running:
*
* FRODO_DEBUG=1 npm run test:update SecretsOps
*
* 3. Test your changes
*
* If 1 and 2 didn't produce any errors, you are ready to run the tests in
* replay mode and make sure they all succeed as well:
*
* FRODO_DEBUG=1 npm run test:only SecretsOps
*
* Note: FRODO_DEBUG=1 is optional and enables debug logging for some output
* in case things don't function as expected
*/

import { autoSetupPolly } from "../../utils/AutoSetupPolly";
import { state } from "../../index";
import * as SecretsOps from './SecretsOps';
import axios, { AxiosError } from "axios";

autoSetupPolly();

async function stageSecret(
secret: {
id: string;
value: string;
description: string;
encoding: string;
useInPlaceholders: boolean;
},
create = true
) {
// delete if exists, then create
try {
await SecretsOps.deleteSecret({ secretId: secret.id, state })
} catch (error) {
// ignore
} finally {
if (create) {
await SecretsOps.createSecret({
secretId: secret.id,
value: secret.value,
description: secret.description,
encoding: secret.encoding,
useInPlaceholders: secret.useInPlaceholders,
state: state
});
}
}
}

describe('SecretsOps', () => {
const secret1 = {
id: 'esv-frodo-test-secret-1',
value: 'value1',
description: 'description1',
encoding: 'generic',
useInPlaceholders: true
}
const secret2 = {
id: 'esv-frodo-test-secret-2',
value: 'value2',
description: 'description2',
encoding: 'generic',
useInPlaceholders: false
}
const secret3 = {
id: 'esv-frodo-test-secret-3',
value: 'value3',
description: 'description3',
encoding: 'generic',
useInPlaceholders: false
}
// in recording mode, setup test data before recording
beforeAll(async () => {
if (process.env.FRODO_POLLY_MODE === 'record') {
await stageSecret(secret1);
await stageSecret(secret2);
await stageSecret(secret3, false);
}
});
// in recording mode, remove test data after recording
afterAll(async () => {
if (process.env.FRODO_POLLY_MODE === 'record') {
await stageSecret(secret1, false);
await stageSecret(secret2, false);
await stageSecret(secret3, false);
}
});

describe('createSecretsExportTemplate()', () => {
test('0: Method is implemented', async () => {
expect(SecretsOps.createSecretsExportTemplate).toBeDefined();
});

test('1: Return template with meta data', async () => {
expect(SecretsOps.createSecretsExportTemplate({ state: state })).toStrictEqual({
meta: expect.any(Object),
secrets: {}
});
});
});

describe('exportSecrets()', () => {
test('0: Method is implemented', async () => {
expect(SecretsOps.exportSecrets).toBeDefined();
});

test('1: Export all secrets', async () => {
const response = await SecretsOps.exportSecrets({ state: state });
expect(response).toMatchSnapshot({
meta: expect.any(Object),
});
});
});

describe('exportSecret()', () => {
test('0: Method is implemented', async () => {
expect(SecretsOps.exportSecret).toBeDefined();
});

test('1: Export secret1', async () => {
const response = await SecretsOps.exportSecret({ secretId: secret1.id, state: state });
expect(response).toMatchSnapshot({
meta: expect.any(Object),
});
});

test('2: Export secret2', async () => {
const response = await SecretsOps.exportSecret({ secretId: secret2.id, state: state });
expect(response).toMatchSnapshot({
meta: expect.any(Object),
});
});

test('3: Export secret3 (non-existent)', async () => {
let errorCaught = false;
try {
await SecretsOps.exportSecret({ secretId: secret3.id, state: state })
} catch (e: any) {
errorCaught = true;
expect(axios.isAxiosError(e)).toBeTruthy();
expect((e as AxiosError).response.status).toBe(404)
}
expect(errorCaught).toBeTruthy();
});
});
});
82 changes: 78 additions & 4 deletions src/ops/cloud/SecretsOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
VersionOfSecretStatus,
} from '../../api/cloud/SecretsApi';
import { State } from '../../shared/State';
import { debugMessage } from '../../utils/Console';
import { getMetadata } from '../../utils/ExportImportUtils';
import { ExportMetaData } from '../OpsTypes';

export type Secret = {
/**
Expand All @@ -27,6 +30,17 @@ export type Secret = {
* @returns {Promise<SecretSkeleton>} a promise that resolves to a secret
*/
readSecret(secretId: string): Promise<SecretSkeleton>;
/**
* Export secret. The response can be saved to file as is.
* @param secretId secret id/name
* @returns {Promise<SecretsExportInterface>} Promise resolving to a SecretsExportInterface object.
*/
exportSecret(secretId): Promise<SecretsExportInterface>;
/**
* Export all secrets
* @returns {Promise<SecretsExportInterface>} Promise resolving to an SecretsExportInterface object.
*/
exportSecrets(): Promise<SecretsExportInterface>;
/**
* Create secret
* @param {string} secretId secret id/name
Expand Down Expand Up @@ -236,7 +250,13 @@ export default (state: State): Secret => {
return readSecrets({ state });
},
async readSecret(secretId: string) {
return _getSecret({ secretId, state });
return readSecret({ secretId, state });
},
async exportSecret(secretId: string): Promise<SecretsExportInterface> {
return exportSecret({ secretId, state });
},
async exportSecrets(): Promise<SecretsExportInterface> {
return exportSecrets({ state });
},
async createSecret(
secretId: string,
Expand Down Expand Up @@ -287,7 +307,7 @@ export default (state: State): Secret => {
return _deleteVersionOfSecret({ secretId, version, state });
},

// Deprecatd
// Deprecated

async getSecrets() {
return readSecrets({ state });
Expand Down Expand Up @@ -338,6 +358,52 @@ export default (state: State): Secret => {
};
};

export interface SecretsExportInterface {
meta?: ExportMetaData;
secrets: Record<string, SecretSkeleton>;
}

export function createSecretsExportTemplate({
state,
}: {
state: State;
}): SecretsExportInterface {
return {
meta: getMetadata({ state }),
secrets: {},
} as SecretsExportInterface;
}

export async function exportSecret({
secretId,
state,
}: {
secretId: string;
state: State;
}): Promise<SecretsExportInterface> {
debugMessage({ message: `SecretsOps.exportSecret: start`, state });
const exportData = createSecretsExportTemplate({ state });
const secret = await _getSecret({ secretId, state });
exportData.secrets[secret._id] = secret;
debugMessage({ message: `VariablesOps.exportSecret: end`, state });
return exportData;
}

export async function exportSecrets({
state,
}: {
state: State;
}): Promise<SecretsExportInterface> {
debugMessage({ message: `SecretsOps.exportSecrets: start`, state });
const exportData = createSecretsExportTemplate({ state });
const secrets = await readSecrets({ state });
for (const secret of secrets) {
exportData.secrets[secret._id] = secret;
}
debugMessage({ message: `SecretsOps.exportSecrets: end`, state });
return exportData;
}

export async function enableVersionOfSecret({
secretId,
version,
Expand Down Expand Up @@ -372,6 +438,16 @@ export async function disableVersionOfSecret({
});
}

export async function readSecret({
secretId,
state,
}: {
secretId: string;
state: State;
}): Promise<SecretSkeleton> {
return await _getSecret({ secretId, state });
}

export async function readSecrets({
state,
}: {
Expand All @@ -385,8 +461,6 @@ export {
_putSecret as createSecret,
_createNewVersionOfSecret as createVersionOfSecret,
_deleteSecret as deleteSecret,
_deleteVersionOfSecret as deleteVersionOfSecret,
_getSecret as readSecret,
_getVersionOfSecret as readVersionOfSecret,
_getSecretVersions as readVersionsOfSecret,
_setSecretDescription as updateSecretDescription,
Expand Down
Loading
Loading