Skip to content

Commit

Permalink
Refactor env:list and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
khamilowicz committed Sep 23, 2024
1 parent a23e973 commit 9023b05
Show file tree
Hide file tree
Showing 2 changed files with 288 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { Config } from '@oclif/core';

import { getMockAppFragment } from '../../../__tests__/commands/utils';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import { testProjectId } from '../../../credentials/__tests__/fixtures-constants';
import {
EnvironmentVariableEnvironment,
EnvironmentVariableFragment,
EnvironmentVariableScope,
EnvironmentVariableVisibility,
} from '../../../graphql/generated';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { EnvironmentVariablesQuery } from '../../../graphql/queries/EnvironmentVariablesQuery';
import EnvironmentVariableList from '../list';
import Log from '../../../log';

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

describe(EnvironmentVariableList, () => {
const graphqlClient = {} as any as ExpoGraphqlClient;
const mockConfig = {} as unknown as Config;

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

it('lists project environment variables successfully', async () => {
const mockVariables: EnvironmentVariableFragment[] = [
{
id: 'var1',
name: 'TEST_VAR_1',
value: 'value1',
environments: [EnvironmentVariableEnvironment.Production],
scope: EnvironmentVariableScope.Project,
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
},
{
id: 'var2',
name: 'TEST_VAR_2',
value: 'value2',
environments: [EnvironmentVariableEnvironment.Development],
scope: EnvironmentVariableScope.Project,
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
},
];

jest.mocked(EnvironmentVariablesQuery.byAppIdAsync).mockResolvedValueOnce(mockVariables);

const command = new EnvironmentVariableList([], mockConfig);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();

expect(EnvironmentVariablesQuery.byAppIdAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: undefined,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
});

it('lists project environment variables including sensitive values', async () => {
const mockVariables: EnvironmentVariableFragment[] = [
{
id: 'var1',
name: 'TEST_VAR_1',
value: 'value1',
environments: [EnvironmentVariableEnvironment.Production],
scope: EnvironmentVariableScope.Project,
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
},
{
id: 'var2',
name: 'TEST_VAR_2',
value: 'value2',
environments: [EnvironmentVariableEnvironment.Development],
scope: EnvironmentVariableScope.Project,
visibility: EnvironmentVariableVisibility.Sensitive,
createdAt: undefined,
updatedAt: undefined,
},
];

jest
.mocked(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync)
.mockResolvedValueOnce(mockVariables);

const command = new EnvironmentVariableList(['--include-sensitive'], mockConfig);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();

expect(EnvironmentVariablesQuery.byAppIdWithSensitiveAsync).toHaveBeenCalledWith(
graphqlClient,
{
appId: testProjectId,
environment: undefined,
}
);
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
});

it('lists shared environment variables successfully', async () => {
const mockVariables: EnvironmentVariableFragment[] = [
{
id: 'var1',
name: 'TEST_VAR_1',
value: 'value1',
environments: [EnvironmentVariableEnvironment.Production],
scope: EnvironmentVariableScope.Shared,
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
},
{
id: 'var2',
name: 'TEST_VAR_2',
value: 'value2',
environments: [EnvironmentVariableEnvironment.Development],
scope: EnvironmentVariableScope.Shared,
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
},
];

jest.mocked(EnvironmentVariablesQuery.sharedAsync).mockResolvedValueOnce(mockVariables);

const command = new EnvironmentVariableList(['--scope', 'shared'], mockConfig);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();

expect(EnvironmentVariablesQuery.sharedAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: undefined,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
});

it('lists shared environment variables including sensitive values', async () => {
const mockVariables: EnvironmentVariableFragment[] = [
{
id: 'var1',
name: 'TEST_VAR_1',
value: 'value1',
environments: [EnvironmentVariableEnvironment.Production],
scope: EnvironmentVariableScope.Shared,
visibility: EnvironmentVariableVisibility.Public,
createdAt: undefined,
updatedAt: undefined,
},
{
id: 'var2',
name: 'TEST_VAR_2',
value: 'value2',
environments: [EnvironmentVariableEnvironment.Development],
scope: EnvironmentVariableScope.Shared,
visibility: EnvironmentVariableVisibility.Sensitive,
createdAt: undefined,
updatedAt: undefined,
},
];

jest
.mocked(EnvironmentVariablesQuery.sharedWithSensitiveAsync)
.mockResolvedValueOnce(mockVariables);

const command = new EnvironmentVariableList(
['--include-sensitive', '--scope', 'shared'],
mockConfig
);

// @ts-expect-error
jest.spyOn(command, 'getContextAsync').mockReturnValue({
loggedIn: { graphqlClient },
privateProjectConfig: { projectId: testProjectId },
});
await command.runAsync();

expect(EnvironmentVariablesQuery.sharedWithSensitiveAsync).toHaveBeenCalledWith(graphqlClient, {
appId: testProjectId,
environment: undefined,
});
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_1'));
expect(Log.log).toHaveBeenCalledWith(expect.stringContaining('TEST_VAR_2'));
});
});
140 changes: 77 additions & 63 deletions packages/eas-cli/src/commands/env/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import {
EASEnvironmentFlag,
EASMultiEnvironmentFlag,
EASVariableFormatFlag,
EASVariableScopeFlag,
} from '../../commandUtils/flags';
Expand All @@ -16,7 +16,42 @@ import {
import { EnvironmentVariablesQuery } from '../../graphql/queries/EnvironmentVariablesQuery';
import Log from '../../log';
import { promptVariableEnvironmentAsync } from '../../utils/prompts';
import { formatVariable } from '../../utils/variableUtils';
import { formatVariable, performForEnvironmentsAsync } from '../../utils/variableUtils';

async function getVariablesForScopeAsync(
graphqlClient: ExpoGraphqlClient,
{
scope,
includingSensitive,
environment,
projectId,
}: {
scope: EnvironmentVariableScope;
includingSensitive: boolean;
environment?: EnvironmentVariableEnvironment;
projectId: string;
}
): Promise<EnvironmentVariableFragment[]> {
if (scope === EnvironmentVariableScope.Project) {
if (includingSensitive) {
return await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
appId: projectId,
environment,
});
}
return await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
appId: projectId,
environment,
});
}

return includingSensitive
? await EnvironmentVariablesQuery.sharedWithSensitiveAsync(graphqlClient, {
appId: projectId,
environment,
})
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, { appId: projectId, environment });
}

export default class EnvironmentValueList extends EasCommand {
static override description = 'list environment variables for the current project';
Expand All @@ -35,12 +70,18 @@ export default class EnvironmentValueList extends EasCommand {
}),
...EASVariableFormatFlag,
...EASVariableScopeFlag,
...EASEnvironmentFlag,
...EASMultiEnvironmentFlag,
};

async runAsync(): Promise<void> {
let {
flags: { environment, format, scope, 'include-sensitive': includeSensitive },
flags: {
environments,
format,
scope,
'include-sensitive': includeSensitive,
'non-interactive': nonInteractive,
},
} = await this.parse(EnvironmentValueList);
const {
privateProjectConfig: { projectId },
Expand All @@ -49,70 +90,43 @@ export default class EnvironmentValueList extends EasCommand {
nonInteractive: true,
});

if (scope === EnvironmentVariableScope.Project && !environment) {
environment = await promptVariableEnvironmentAsync({ nonInteractive: false });
if (!environments) {
environments = await promptVariableEnvironmentAsync({ nonInteractive, multiple: true });
}

const variables = await this.getVariablesForScopeAsync(graphqlClient, {
scope,
includingSensitive: includeSensitive,
environment,
projectId,
});
await performForEnvironmentsAsync(environments, async environment => {
const variables = await getVariablesForScopeAsync(graphqlClient, {
scope,
includingSensitive: includeSensitive,
environment,
projectId,
});

if (format === 'short') {
for (const variable of variables) {
// TODO: Add Learn more link
Log.log(
`${chalk.bold(variable.name)}=${
variable.value ??
"***** (This is a secret env variable that can only be accessed on EAS builder and can't be read in any UI. Learn more.)"
}`
);
Log.addNewLineIfNone();
if (environment) {
Log.log(chalk.bold(`Environment: ${environment}`));
}
} else {
if (scope === EnvironmentVariableScope.Shared) {
Log.log(chalk.bold('Shared variables for this account:'));
} else {
Log.log(chalk.bold(`Variables for this project for environment ${environment}:`));
}
Log.log(
variables.map(variable => formatVariable(variable)).join(`\n\n${chalk.dim('———')}\n\n`)
);
}
}

private async getVariablesForScopeAsync(
graphqlClient: ExpoGraphqlClient,
{
scope,
includingSensitive,
environment,
projectId,
}: {
scope: EnvironmentVariableScope;
includingSensitive: boolean;
environment?: EnvironmentVariableEnvironment;
projectId: string;
}
): Promise<EnvironmentVariableFragment[]> {
if (scope === EnvironmentVariableScope.Project && environment) {
if (includingSensitive) {
return await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
appId: projectId,
environment,
});
if (format === 'short') {
for (const variable of variables) {
// TODO: Add Learn more link
Log.log(
`${chalk.bold(variable.name)}=${
variable.value ??
"***** (This is a secret env variable that can only be accessed on EAS builder and can't be read in any UI. Learn more.)"
}`
);
}
} else {
if (scope === EnvironmentVariableScope.Shared) {
Log.log(chalk.bold('Shared variables for this account:'));
} else {
Log.log(chalk.bold(`Variables for this project:`));
}
Log.log(
variables.map(variable => formatVariable(variable)).join(`\n\n${chalk.dim('———')}\n\n`)
);
}
return await EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
appId: projectId,
environment,
});
}

return includingSensitive
? await EnvironmentVariablesQuery.sharedWithSensitiveAsync(graphqlClient, {
appId: projectId,
})
: await EnvironmentVariablesQuery.sharedAsync(graphqlClient, { appId: projectId });
});
}
}

0 comments on commit 9023b05

Please sign in to comment.