Skip to content

Commit

Permalink
Refactor env:unlink, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
khamilowicz committed Sep 20, 2024
1 parent 6957021 commit 34beb40
Show file tree
Hide file tree
Showing 2 changed files with 309 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { Config } from '@oclif/core';
import chalk from 'chalk';

import { getMockAppFragment } from '../../../__tests__/commands/utils';
import {
EnvironmentVariableEnvironment,
EnvironmentVariableScope,
} from '../../../graphql/generated';
import { EnvironmentVariableMutation } from '../../../graphql/mutations/EnvironmentVariableMutation';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../../log';
import { promptAsync, selectAsync, toggleConfirmAsync } from '../../../prompts';
import EnvironmentVariableUnlink from '../unlink';

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

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

const successMessage = (env: EnvironmentVariableEnvironment) =>
`Unlinked variable ${chalk.bold('TEST_VARIABLE')} from project ${chalk.bold(
'@testuser/testpp'
)} in ${env}.`;

beforeEach(() => {
jest.resetAllMocks();
jest.mocked(AppQuery.byIdAsync).mockImplementation(async () => getMockAppFragment());
});

it('unlinks a shared variable from the current project in non-interactive mode', async () => {
const mockVariables = [
{
id: variableId,
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Shared,
environments: [EnvironmentVariableEnvironment.Development],
linkedEnvironments: [EnvironmentVariableEnvironment.Development],
},
];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
(
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
).mockResolvedValue(mockVariables[0]);

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

expect(EnvironmentVariablesQuery.sharedAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
filterNames: ['TEST_VARIABLE'],
});
expect(EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId
);
expect(Log.withTick).toHaveBeenCalledWith(
successMessage(EnvironmentVariableEnvironment.Development)
);
});

it('unlinks a shared variable from the current project in a specified environment', async () => {
const mockVariables = [
{
id: variableId,
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Shared,
environments: [EnvironmentVariableEnvironment.Development],
linkedEnvironments: [EnvironmentVariableEnvironment.Production],
},
];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
(
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
).mockResolvedValue(mockVariables[0]);

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

expect(Log.withTick).toHaveBeenCalledWith(
successMessage(EnvironmentVariableEnvironment.Production)
);
expect(EnvironmentVariablesQuery.sharedAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
filterNames: ['TEST_VARIABLE'],
});
expect(EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId,
EnvironmentVariableEnvironment.Production
);
});

it('prompts for variable selection when the name is ambigous', async () => {
const mockVariables = [
{
id: variableId,
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Shared,
environments: [EnvironmentVariableEnvironment.Preview],
linkedEnvironments: [EnvironmentVariableEnvironment.Preview],
},
{
id: 'other-id',
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Shared,
environments: [EnvironmentVariableEnvironment.Development],
linkedEnvironments: [EnvironmentVariableEnvironment.Development],
},
];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
(
EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync as jest.Mock
).mockResolvedValue(mockVariables[0]);
(selectAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
(promptAsync as jest.Mock).mockResolvedValue({
environments: [],
});
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);

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

expect(selectAsync).toHaveBeenCalled();
expect(EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId,
EnvironmentVariableEnvironment.Preview
);
expect(Log.withTick).toHaveBeenCalledWith(
successMessage(EnvironmentVariableEnvironment.Preview)
);
});

it('throws an error when variable name is not found', async () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);

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

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
);
});

it('uses environments from prompt to both link and unlink environments', async () => {
const mockVariables = [
{
id: variableId,
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Shared,
environments: [EnvironmentVariableEnvironment.Preview],
linkedEnvironments: [EnvironmentVariableEnvironment.Preview],
},
];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);
(EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync as jest.Mock).mockResolvedValue(
mockVariables[0]
);
(selectAsync as jest.Mock).mockResolvedValue(mockVariables[0]);
(promptAsync as jest.Mock).mockResolvedValue({
environments: [EnvironmentVariableEnvironment.Production],
});
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);

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

expect(promptAsync).toHaveBeenCalled();
expect(EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId,
EnvironmentVariableEnvironment.Production
);
expect(EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId,
EnvironmentVariableEnvironment.Preview
);
});

it('throws an error when variable name is not found', async () => {
const mockVariables: never[] = [];
(EnvironmentVariablesQuery.sharedAsync as jest.Mock).mockResolvedValue(mockVariables);

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

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue(mockContext);
await expect(command.runAsync()).rejects.toThrow(
"Shared variable NON_EXISTENT_VARIABLE doesn't exist"
);
});
});
119 changes: 83 additions & 36 deletions packages/eas-cli/src/commands/env/unlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { Flags } from '@oclif/core';
import chalk from 'chalk';

import EasCommand from '../../commandUtils/EasCommand';
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
import { EnvironmentVariableScope } from '../../graphql/generated';
import { EASMultiEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
import { EnvironmentVariableEnvironment } from '../../graphql/generated';
import { EnvironmentVariableMutation } from '../../graphql/mutations/EnvironmentVariableMutation';
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { getDisplayNameForProjectIdAsync } from '../../project/projectUtils';
import { selectAsync } from '../../prompts';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { formatVariableName } from '../../utils/variableUtils';

export default class EnvironmentVariableUnlink extends EasCommand {
static override description = 'unlink a shared environment variable to the current project';
Expand All @@ -20,7 +21,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {
name: Flags.string({
description: 'Name of the variable',
}),
...EASEnvironmentFlag,
...EASMultiEnvironmentFlag,
...EASNonInteractiveFlag,
};

Expand All @@ -31,7 +32,7 @@ export default class EnvironmentVariableUnlink extends EasCommand {

async runAsync(): Promise<void> {
let {
flags: { name, 'non-interactive': nonInteractive, environment },
flags: { name, 'non-interactive': nonInteractive, environment: unlinkEnvironments },
} = await this.parse(EnvironmentVariableUnlink);
const {
privateProjectConfig: { projectId },
Expand All @@ -45,52 +46,98 @@ export default class EnvironmentVariableUnlink extends EasCommand {
}

const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);

const appVariables = await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
const variables = await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
appId: projectId,
environment,
filterNames: name ? [name] : undefined,
});
const linkedVariables = appVariables.filter(
({ scope }) => scope === EnvironmentVariableScope.Shared
);

if (linkedVariables.length === 0) {
throw new Error(`There are no linked shared env variables for project ${projectDisplayName}`);
}

if (!name) {
name = await selectAsync(
'Select shared variable to unlink',
linkedVariables.map(variable => ({
title: variable.name,
value: variable.name,
let selectedVariable = variables[0];

if (variables.length > 1) {
if (nonInteractive) {
throw new Error("Multiple variables found, please select one using '--name'");
}
selectedVariable = await selectAsync(
'Select shared variable',
variables.map(variable => ({
title: formatVariableName(variable),
value: variable,
}))
);
}

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

if (!selectedVariable) {
throw new Error(`Shared variable ${name} doesn't exist`);
}

const unlinkedVariable = await EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId,
environment
);
let explicitSelect = false;

if (!nonInteractive && !unlinkEnvironments) {
const selectedEnvironments =
(selectedVariable.linkedEnvironments ?? []).length > 0
? selectedVariable.linkedEnvironments
: selectedVariable.environments;
const environments = await promptVariableEnvironmentAsync({
nonInteractive,
multiple: true,
selectedEnvironments: selectedEnvironments ?? [],
});
explicitSelect = true;
unlinkEnvironments = Object.values(EnvironmentVariableEnvironment).filter(
env => !environments.includes(env)
);
}

if (!unlinkedVariable) {
throw new Error(
`Could not unlink variable with name ${selectedVariable.name} from project ${projectDisplayName}`
if (!unlinkEnvironments) {
await EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId
);
Log.withTick(
`Unlinked variable ${chalk.bold(selectedVariable.name)} from project ${chalk.bold(
projectDisplayName
)} in ${selectedVariable.environments?.join(', ')}.`
);
return;
}

Log.withTick(
`Unlinked variable ${chalk.bold(unlinkedVariable.name)} from the project ${chalk.bold(
projectDisplayName
)}.`
);
for (const environment of Object.values(EnvironmentVariableEnvironment)) {
try {
if (
selectedVariable.linkedEnvironments?.includes(environment) !==
unlinkEnvironments.includes(environment)
) {
continue;
}
if (unlinkEnvironments.includes(environment)) {
await EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId,
environment
);
Log.withTick(
`Unlinked variable ${chalk.bold(selectedVariable.name)} from project ${chalk.bold(
projectDisplayName
)} in ${environment}.`
);
} else if (explicitSelect) {
await EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId,
environment
);
Log.withTick(
`Linked variable ${chalk.bold(selectedVariable.name)} to project ${chalk.bold(
projectDisplayName
)} in ${environment}.`
);
}
} catch (err: any) {
Log.warn(err.message);
}
}
}
}

0 comments on commit 34beb40

Please sign in to comment.