Skip to content

Commit

Permalink
Add tests for EnvironmentVariableDelete
Browse files Browse the repository at this point in the history
  • Loading branch information
khamilowicz committed Sep 27, 2024
1 parent 549e007 commit 11957c3
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Config } from '@oclif/core';

import {
EnvironmentVariableEnvironment,
EnvironmentVariableScope,
} from '../../../graphql/generated';
import { EnvironmentVariableMutation } from '../../../graphql/mutations/EnvironmentVariableMutation';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../../log';
import { promptAsync, toggleConfirmAsync } from '../../../prompts';
import EnvironmentVariableDelete from '../delete';

jest.mock('../../../graphql/queries/EnvironmentVariablesQuery');
jest.mock('../../../graphql/mutations/EnvironmentVariableMutation');
jest.mock('../../../prompts');
jest.mock('../../../log');

describe(EnvironmentVariableDelete, () => {
const projectId = 'test-project-id';
const variableId = '1';
const graphqlClient = {};
const mockConfig = {} as unknown as Config;
const mockContext = {
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
};

beforeEach(() => {
jest.resetAllMocks();
});

it('deletes a variable by name in non-interactive mode', async () => {
const mockVariables = [
{
id: variableId,
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Project,
environments: [EnvironmentVariableEnvironment.Production],
},
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);

const command = new EnvironmentVariableDelete(
[
'--variable-name',
'TEST_VARIABLE',
'--variable-environment',
'production',
'--non-interactive',
],
mockConfig
);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();

expect(EnvironmentVariableMutation.deleteAsync).toHaveBeenCalledWith(graphqlClient, variableId);
expect(Log.withTick).toHaveBeenCalledWith('️Deleted variable TEST_VARIABLE".');
});

it('prompts for variable selection when name is not provided', async () => {
const mockVariables = [
{ id: variableId, name: 'TEST_VARIABLE', scope: EnvironmentVariableScope.Project },
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);

const command = new EnvironmentVariableDelete([], mockConfig);
// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await command.runAsync();

expect(promptAsync).toHaveBeenCalled();
expect(EnvironmentVariableMutation.deleteAsync).toHaveBeenCalledWith(graphqlClient, variableId);
expect(Log.withTick).toHaveBeenCalledWith('️Deleted variable TEST_VARIABLE".');
});

it('throws an error when variable name is not found', async () => {
const mockVariables = [
{ id: variableId, name: 'TEST_VARIABLE', scope: EnvironmentVariableScope.Project },
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);

const command = new EnvironmentVariableDelete(
['--variable-name', 'NON_EXISTENT_VARIABLE'],
mockConfig
);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow('Variable "NON_EXISTENT_VARIABLE" not found.');
});

it('throws an error when multiple variables with the same name are found', async () => {
const mockVariables = [
{ id: variableId, name: 'TEST_VARIABLE', scope: EnvironmentVariableScope.Project },
{ id: '2', name: 'TEST_VARIABLE', scope: EnvironmentVariableScope.Project },
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);

const command = new EnvironmentVariableDelete(['--variable-name', 'TEST_VARIABLE'], mockConfig);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
'Multiple variables with name "TEST_VARIABLE" found. Please select the variable to delete interactively or run command with --variable-environment ENVIRONMENT option.'
);
});

it('cancels deletion when user does not confirm', async () => {
const mockVariables = [
{ id: variableId, name: 'TEST_VARIABLE', scope: EnvironmentVariableScope.Project },
];
(EnvironmentVariablesQuery.byAppIdAsync as jest.Mock).mockResolvedValue(mockVariables);
(promptAsync as jest.Mock).mockResolvedValue({ variable: mockVariables[0] });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(false);

const command = new EnvironmentVariableDelete(['--non-interactive'], mockConfig);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrowErrorMatchingSnapshot();

expect(EnvironmentVariableMutation.deleteAsync).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EnvironmentVariableDelete cancels deletion when user does not confirm 1`] = `"Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with --variable-name VARIABLE_NAME flag to fix the issue"`;
87 changes: 51 additions & 36 deletions packages/eas-cli/src/commands/env/delete.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { Flags } from '@oclif/core';
import assert from 'assert';
import chalk from 'chalk';

import EasCommand from '../../commandUtils/EasCommand';
import {
EASEnvironmentFlag,
EASNonInteractiveFlag,
EASVariableScopeFlag,
EasEnvironmentFlagParameters,
} from '../../commandUtils/flags';
import { EnvironmentVariableEnvironment, EnvironmentVariableScope } from '../../graphql/generated';
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { promptAsync, toggleConfirmAsync } from '../../prompts';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { formatVariableName } from '../../utils/variableUtils';

type DeleteFlags = {
name?: string;
environment?: EnvironmentVariableEnvironment;
'variable-name'?: string;
'variable-environment'?: EnvironmentVariableEnvironment;
'non-interactive': boolean;
scope?: EnvironmentVariableScope;
};
Expand All @@ -27,11 +28,14 @@ export default class EnvironmentVariableDelete extends EasCommand {
static override hidden = true;

static override flags = {
name: Flags.string({
'variable-name': Flags.string({
description: 'Name of the variable to delete',
}),
'variable-environment': Flags.enum<EnvironmentVariableEnvironment>({
...EasEnvironmentFlagParameters,
description: 'Current environment of the variable to delete',
}),
...EASVariableScopeFlag,
...EASEnvironmentFlag,
...EASNonInteractiveFlag,
};

Expand All @@ -42,56 +46,67 @@ export default class EnvironmentVariableDelete extends EasCommand {

async runAsync(): Promise<void> {
const { flags } = await this.parse(EnvironmentVariableDelete);
let { name, environment, 'non-interactive': nonInteractive, scope } = this.validateFlags(flags);
const {
'variable-name': name,
'variable-environment': environment,
'non-interactive': nonInteractive,
scope,
} = this.validateFlags(flags);
const {
privateProjectConfig: { projectId },
loggedIn: { graphqlClient },
} = await this.getContextAsync(EnvironmentVariableDelete, {
nonInteractive,
});

if (scope === EnvironmentVariableScope.Project) {
if (!environment) {
environment = await promptVariableEnvironmentAsync({ nonInteractive });
}
}

const variables =
scope === EnvironmentVariableScope.Project && environment
scope === EnvironmentVariableScope.Project
? await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
appId: projectId,
environment,
})
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, { appId: projectId });
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
appId: projectId,
environment,
});

let selectedVariable;

if (!name) {
({ name } = await promptAsync({
({ variable: selectedVariable } = await promptAsync({
type: 'select',
name: 'name',
name: 'variable',
message: 'Pick the variable to be deleted:',
choices: variables
.filter(({ scope: variableScope }) => scope === variableScope)
.map(variable => ({
title: variable.name,
value: variable.name,
})),
.map(variable => {
return {
title: formatVariableName(variable),
value: variable,
};
}),
}));

if (!name) {
throw new Error(
`Environment variable wasn't selected. Run the command again and select existing variable or run it with ${chalk.bold(
'--name VARIABLE_NAME'
)} flag to fix the issue.`
);
} else {
const selectedVariables = variables.filter(
variable =>
variable.name === name && (!environment || variable.environments?.includes(environment))
);

if (selectedVariables.length !== 1) {
if (selectedVariables.length === 0) {
throw new Error(`Variable "${name}" not found.`);
} else {
throw new Error(
`Multiple variables with name "${name}" found. Please select the variable to delete interactively or run command with --variable-environment ENVIRONMENT option.`
);
}
}
}

const selectedVariable = variables.find(variable => variable.name === name);

if (!selectedVariable) {
throw new Error(`Variable "${name}" not found.`);
selectedVariable = selectedVariables[0];
}

assert(selectedVariable, `Variable "${name}" not found.`);

if (!nonInteractive) {
Log.addNewLineIfNone();
Log.warn(`You are about to permanently delete variable ${selectedVariable.name}.`);
Expand All @@ -106,7 +121,7 @@ export default class EnvironmentVariableDelete extends EasCommand {
});
if (!confirmed) {
Log.error(`Canceled deletion of variable ${selectedVariable.name}.`);
throw new Error(`Variable "${name}" not deleted.`);
throw new Error(`Variable "${selectedVariable.name}" not deleted.`);
}
}

Expand All @@ -117,10 +132,10 @@ export default class EnvironmentVariableDelete extends EasCommand {

private validateFlags(flags: DeleteFlags): DeleteFlags {
if (flags['non-interactive']) {
if (!flags.name) {
if (!flags['variable-name']) {
throw new Error(
`Environment variable needs 'name' to be specified when running in non-interactive mode. Run the command with ${chalk.bold(
'--name VARIABLE_NAME'
'--variable-name VARIABLE_NAME'
)} flag to fix the issue`
);
}
Expand Down
14 changes: 13 additions & 1 deletion packages/eas-cli/src/utils/variableUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import dateFormat from 'dateformat';

import formatFields from './formatFields';
import { EnvironmentVariableEnvironment, EnvironmentVariableFragment } from '../graphql/generated';
import {
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
} from '../graphql/generated';

export function formatVariableName(variable: EnvironmentVariableFragment): string {
const name = variable.name;
const scope = variable.scope === EnvironmentVariableScope.Project ? 'project' : 'shared';
const environments = variable.environments?.join(', ') ?? '';
const updatedAt = variable.updatedAt ? new Date(variable.updatedAt).toLocaleString() : '';
return `${name} | ${scope} | ${environments} | Updated at: ${updatedAt}`;
}

export async function performForEnvironmentsAsync(
environments: EnvironmentVariableEnvironment[] | null,
Expand Down

0 comments on commit 11957c3

Please sign in to comment.