Skip to content

Commit

Permalink
Refactor env:link, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
khamilowicz committed Sep 20, 2024
1 parent e039833 commit 5cb484a
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
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 EnvironmentVariableLink from '../link';

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

describe(EnvironmentVariableLink, () => {
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) =>

Check warning on line 32 in packages/eas-cli/src/commands/env/__tests__/EnvironmentVariableLink.test.ts

View workflow job for this annotation

GitHub Actions / Test with Node 22

Missing return type on function
`Linked variable ${chalk.bold('TEST_VARIABLE')} to project ${chalk.bold(
'@testuser/testpp'
)} in ${env}.`;

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

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

const command = new EnvironmentVariableLink(
['--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.linkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId
);
expect(Log.withTick).toHaveBeenCalledWith(
successMessage(EnvironmentVariableEnvironment.Development)
);
});

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

const command = new EnvironmentVariableLink(
['--name', 'TEST_VARIABLE', '--environment', 'production', '--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.linkSharedEnvironmentVariableAsync).toHaveBeenCalledWith(
graphqlClient,
variableId,
projectId,
EnvironmentVariableEnvironment.Production
);
expect(Log.withTick).toHaveBeenCalledWith(
successMessage(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],
},
{
id: 'other-id',
name: 'TEST_VARIABLE',
scope: EnvironmentVariableScope.Shared,
environments: [EnvironmentVariableEnvironment.Development],
},
];
(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: mockVariables[0].environments });
(toggleConfirmAsync as jest.Mock).mockResolvedValue(true);

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

expect(selectAsync).toHaveBeenCalled();
expect(EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync).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 EnvironmentVariableLink(['--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 EnvironmentVariableLink([], 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 EnvironmentVariableLink(['--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"
);
});
});
97 changes: 72 additions & 25 deletions packages/eas-cli/src/commands/env/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { Flags } from '@oclif/core';
import chalk from 'chalk';

import EasCommand from '../../commandUtils/EasCommand';
import { EASEnvironmentFlag, EASNonInteractiveFlag } from '../../commandUtils/flags';
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 EnvironmentVariableLink extends EasCommand {
static override description = 'link a shared environment variable to the current project';
Expand All @@ -19,7 +21,7 @@ export default class EnvironmentVariableLink extends EasCommand {
name: Flags.string({
description: 'Name of the variable',
}),
...EASEnvironmentFlag,
...EASMultiEnvironmentFlag,
...EASNonInteractiveFlag,
};

Expand All @@ -30,7 +32,7 @@ export default class EnvironmentVariableLink extends EasCommand {

async runAsync(): Promise<void> {
let {
flags: { name, 'non-interactive': nonInteractive, environment },
flags: { name, 'non-interactive': nonInteractive, environment: environments },
} = await this.parse(EnvironmentVariableLink);
const {
privateProjectConfig: { projectId },
Expand All @@ -42,44 +44,89 @@ export default class EnvironmentVariableLink extends EasCommand {
const projectDisplayName = await getDisplayNameForProjectIdAsync(graphqlClient, projectId);
const variables = await EnvironmentVariablesQuery.sharedAsync(graphqlClient, {
appId: projectId,
filterNames: name ? [name] : undefined,
});

if (!name) {
name = await selectAsync(
let selectedVariable = variables[0];

if (variables.length > 1) {
selectedVariable = await selectAsync(
'Select shared variable',
variables.map(variable => ({
title: variable.name,
value: variable.name,
title: formatVariableName(variable),
value: variable,
}))
);
}

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

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

if (!environment) {
environment = await promptVariableEnvironmentAsync({ nonInteractive });
let explicitSelect = false;
if (!nonInteractive && !environments) {
const selectedEnvironments =
(selectedVariable.linkedEnvironments ?? []).length > 0
? selectedVariable.linkedEnvironments
: selectedVariable.environments;
environments = await promptVariableEnvironmentAsync({
nonInteractive,
multiple: true,
selectedEnvironments: selectedEnvironments ?? [],
});
explicitSelect = true;
}

const linkedVariable = await EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId,
environment
);
if (!linkedVariable) {
throw new Error(
`Could not link variable with name ${selectedVariable.name} to project with id ${projectId}`
if (!environments) {
await EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId
);
Log.withTick(
`Linked variable ${chalk.bold(selectedVariable.name)} to project ${chalk.bold(
projectDisplayName
)} in ${selectedVariable.environments?.join(', ')}.`
);
return;
}

Log.withTick(
`Linked variable ${chalk.bold(linkedVariable.name)} to project ${chalk.bold(
projectDisplayName
)}.`
);
for (const environment of Object.values(EnvironmentVariableEnvironment)) {
try {
if (
selectedVariable.linkedEnvironments?.includes(environment) ===
environments.includes(environment)
) {
continue;
}
if (environments.includes(environment)) {
await EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId,
environment
);
Log.withTick(
`Linked variable ${chalk.bold(selectedVariable.name)} to project ${chalk.bold(
projectDisplayName
)} in ${environment}.`
);
} else if (explicitSelect) {
await EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(
graphqlClient,
selectedVariable.id,
projectId,
environment
);
Log.withTick(
`Unlinked variable ${chalk.bold(selectedVariable.name)} from project ${chalk.bold(
projectDisplayName
)} in ${environment}.`
);
}
} catch (err: any) {
Log.warn(err.message);
}
}
}
}
4 changes: 2 additions & 2 deletions packages/eas-cli/src/graphql/generated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5cb484a

Please sign in to comment.