Skip to content

Commit

Permalink
feat(cli): use api key in .env file (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
y-lakhdar authored Apr 12, 2021
1 parent 8cef023 commit 2a48f0d
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 43 deletions.
11 changes: 8 additions & 3 deletions packages/cli-e2e/__tests__/angular.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ describe('ui', () => {
let processManager: ProcessManager;
// TODO: CDX-90: Assign a dynamic port for the search token server on all ui projects
const clientPort = '4200';
const projectName = 'angular-project';
const projectName = `${process.env.GITHUB_ACTION}-angular-project`;
const searchPageEndpoint = `http://localhost:${clientPort}`;
const tokenProxyEndpoint = `http://localhost:${clientPort}/token`;
let interceptedRequests: HTTPRequest[] = [];
let page: Page;

const searchboxSelector = 'app-search-page app-search-box input';

beforeAll(async () => {
processManager = new ProcessManager();
browser = await getNewBrowser();
Expand Down Expand Up @@ -103,6 +105,7 @@ describe('ui', () => {
await page.goto(searchPageEndpoint, {
waitUntil: 'networkidle2',
});
await page.waitForSelector(searchboxSelector);

expect(await page.$('app-search-page')).not.toBeNull();
});
Expand All @@ -111,6 +114,7 @@ describe('ui', () => {
const tokenResponseListener = page.waitForResponse(tokenProxyEndpoint);

page.goto(searchPageEndpoint);
await page.waitForSelector(searchboxSelector);

expect(
JSON.parse(await (await tokenResponseListener).text())
Expand All @@ -121,16 +125,17 @@ describe('ui', () => {

it('should send a search query when the page is loaded', async () => {
await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
await page.waitForSelector(searchboxSelector);

expect(interceptedRequests.some(isSearchRequest)).toBeTruthy();
});

it('should send a search query on searchbox submit', async () => {
const searchboxSelector = 'app-search-page app-search-box input';
await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
await page.waitForSelector(searchboxSelector);

interceptedRequests = [];

await page.waitForSelector(searchboxSelector);
await page.focus(searchboxSelector);
await page.keyboard.type('my query');
await page.keyboard.press('Enter');
Expand Down
20 changes: 14 additions & 6 deletions packages/cli-e2e/__tests__/react.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ describe('ui', () => {
let processManager: ProcessManager;
// TODO: CDX-90: Assign a dynamic port for the search token server on all ui projects
const clientPort = '3000';
const projectName = 'react-project';
const projectName = `${process.env.GITHUB_ACTION}-react-project`;
const searchPageEndpoint = `http://localhost:${clientPort}`;
const tokenProxyEndpoint = `http://localhost:${clientPort}/token`;
let interceptedRequests: HTTPRequest[] = [];
let page: Page;
const searchboxSelector = 'div.App .MuiAutocomplete-root input';

beforeAll(async () => {
browser = await getNewBrowser();
Expand All @@ -41,7 +42,7 @@ describe('ui', () => {
await new Promise<void>((resolve) => {
startServerProcess.stdout.on('data', async (data) => {
if (
/You can now view react-project in the browser/.test(
/You can now view .*-react-project in the browser/.test(
stripAnsi(data.toString()).replace(/\n/g, '')
)
) {
Expand Down Expand Up @@ -73,30 +74,37 @@ describe('ui', () => {
await page.goto(searchPageEndpoint, {
waitUntil: 'networkidle2',
});
await page.waitForSelector(searchboxSelector);

expect(await page.$('div.App')).not.toBeNull();
});

it('should retrieve the search token on the page load', async () => {
const tokenResponseListener = page.waitForResponse(tokenProxyEndpoint);

page.goto(searchPageEndpoint);
const tokenResponse = await page.waitForResponse(tokenProxyEndpoint);
expect(JSON.parse(await tokenResponse.text())).toMatchObject({
await page.waitForSelector(searchboxSelector);

expect(
JSON.parse(await (await tokenResponseListener).text())
).toMatchObject({
token: expect.stringMatching(/^eyJhb.+/),
});
});

it('should send a search query when the page is loaded', async () => {
await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
await page.waitForSelector(searchboxSelector);

expect(interceptedRequests.some(isSearchRequest)).toBeTruthy();
});

it('should send a search query on searchbox submit', async () => {
const searchboxSelector = 'div.App .MuiAutocomplete-root input';
await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
await page.waitForSelector(searchboxSelector);

interceptedRequests = [];

await page.waitForSelector(searchboxSelector);
await page.focus(searchboxSelector);
await page.keyboard.type('my query');
await page.keyboard.press('Enter');
Expand Down
24 changes: 17 additions & 7 deletions packages/cli-e2e/__tests__/vue.specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ describe('ui', () => {
let browser: Browser;
// TODO: CDX-90: Assign a dynamic port for the search token server on all ui projects
const clientPort = '8080';
const projectName = 'vue-project';
const projectName = `${process.env.GITHUB_ACTION}-vue-project`;
const searchPageEndpoint = `http://localhost:${clientPort}`;
const tokenProxyEndpoint = `http://localhost:${clientPort}/token`;
let interceptedRequests: HTTPRequest[] = [];
let page: Page;
let processManager: ProcessManager;
const searchboxSelector = '#search-page .autocomplete input';

beforeAll(async () => {
processManager = new ProcessManager();
Expand Down Expand Up @@ -101,27 +102,36 @@ describe('ui', () => {
await page.goto(searchPageEndpoint, {
waitUntil: 'networkidle2',
});
await page.waitForSelector(searchboxSelector);

expect(await page.$('#search-page')).not.toBeNull();
});

it('should retrieve the search token on the page load', async () => {
const tokenResponseListener = page.waitForResponse(tokenProxyEndpoint);
page.goto(searchPageEndpoint);
const tokenResponse = await page.waitForResponse(tokenProxyEndpoint);
expect(JSON.parse(await tokenResponse.text())).toMatchObject({
await page.waitForSelector(searchboxSelector);

expect(
JSON.parse(await (await tokenResponseListener).text())
).toMatchObject({
token: expect.stringMatching(/^eyJhb.+/),
});
});

it('should trigger search queries', async () => {
const searchboxSelector = '#search-page .autocomplete input';
it('should send a search query when the page is loaded', async () => {
await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
await page.waitForSelector(searchboxSelector);

// Request from interface load
expect(interceptedRequests.some(isSearchRequest)).toBeTruthy();
interceptedRequests = [];
});

it('should send a search query on searchbox submit', async () => {
await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
await page.waitForSelector(searchboxSelector);

interceptedRequests = [];

await page.focus(searchboxSelector);
await page.keyboard.type('my query');
await page.keyboard.press('Enter');
Expand Down
8 changes: 6 additions & 2 deletions packages/cli-e2e/entrypoints/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,19 @@ const ensureDockerImageIsPresent = () => {
};

const createEnvFile = () => {
const credentials = ['PLATFORM_USER_NAME', 'PLATFORM_USER_PASSWORD'];
const environmentVariables = [
'PLATFORM_USER_NAME',
'PLATFORM_USER_PASSWORD',
'GITHUB_ACTION',
];

if (existsSync('.env')) {
return;
}

writeFileSync(
'.env',
credentials
environmentVariables
.map((variable) => `${variable}=${process.env[variable]}`)
.join('\n')
);
Expand Down
3 changes: 3 additions & 0 deletions packages/cli-e2e/setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {mkdirSync} from 'fs';
import {randomBytes} from 'crypto';
import type {Browser} from 'puppeteer';
import {
captureScreenshots,
Expand All @@ -22,6 +23,8 @@ async function clearChromeBrowsingData(browser: Browser) {

export default async function () {
mkdirSync(SCREENSHOTS_PATH, {recursive: true});
process.env.GITHUB_ACTION =
process.env.GITHUB_ACTION || randomBytes(16).toString('hex');
const browser = await connectToChromeBrowser();
await clearChromeBrowsingData(browser);
await clearAccessTokenFromConfig();
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-e2e/teardown.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {closeAllPages, connectToChromeBrowser} from './utils/browser';
import {deleteAllCliApiKeys} from './utils/cli';

export default async function () {
const browser = await connectToChromeBrowser();
const pageClosePromises = await closeAllPages(browser);
await deleteAllCliApiKeys();
if (global.processManager) {
await global.processManager.killAllProcesses();
}
Expand Down
46 changes: 46 additions & 0 deletions packages/cli-e2e/utils/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {homedir} from 'os';
import stripAnsi from 'strip-ansi';

import {ProcessManager} from './processManager';
import {readJsonSync} from 'fs-extra';
import type {AxiosResponse} from 'axios';
import axios from 'axios';

export function isYesNoPrompt(data: string) {
return data.trimEnd().toLowerCase().endsWith('(y/n):');
Expand Down Expand Up @@ -67,4 +70,47 @@ export function setupUIProject(
return buildProcess;
}

export function getConfig() {
const pathToConfig = resolve(
homedir(),
'.config',
'@coveo',
'cli',
'config.json'
);

return readJsonSync(pathToConfig);
}

export async function deleteAllCliApiKeys() {
const {organization, accessToken} = getConfig();

// TODO: CDX-98: connect to prod environment.
const hostname = 'https://platformdev.cloud.coveo.com';
const baseUrl = `/rest/organizations/${organization}/apikeys`;
const authHeader = {
headers: {Authorization: `Bearer ${accessToken}`},
};

const response: AxiosResponse<Record<string, unknown>[]> = await axios.get(
`${hostname}${baseUrl}`,
authHeader
);

const KeysToDelete = response.data
.filter(
(key) =>
(key.displayName as string)?.startsWith(
`cli-${process.env.GITHUB_ACTION}-`
) && key.description === 'Generated by the Coveo CLI'
)
.map((key) => key.id);

await Promise.all(
KeysToDelete.map((keyId) =>
axios.delete(`${hostname}${baseUrl}/${keyId}`, authHeader)
)
);
}

export const CLI_EXEC_PATH = resolve(__dirname, '../../cli/bin/run');
1 change: 1 addition & 0 deletions packages/cli/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ module.exports = {
collectCoverage: true,
clearMocks: true,
silent: true,
testTimeout: 60e3,
};
32 changes: 23 additions & 9 deletions packages/cli/src/commands/ui/create/angular.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export default class Angular extends Command {

private async addCoveoToProject(applicationName: string, defaults: boolean) {
const cfg = await this.configuration.get();
const {providerUsername} = await this.getUserInfo();
const {flags} = this.parse(Angular);
const {userInfo, apiKey} = await this.platformUserCredentials();
const flags = this.flags;
const schematicVersion =
flags.version || getPackageVersion(Angular.templateName);

Expand All @@ -83,11 +83,11 @@ export default class Angular extends Command {
'--org-id',
cfg.organization,
'--api-key',
cfg.accessToken!,
apiKey.value!,
'--platform-url',
platformUrl({environment: cfg.environment}),
'--user',
providerUsername,
userInfo.providerUsername,
];

if (defaults) {
Expand All @@ -103,7 +103,7 @@ export default class Angular extends Command {
}

async catch(err?: Error) {
const {flags} = this.parse(Angular);
const flags = this.flags;
await this.config.runHook(
'analytics',
buildAnalyticsFailureHook(this, flags, err)
Expand All @@ -115,21 +115,35 @@ export default class Angular extends Command {
return new Config(this.config.configDir, this.error);
}

private async getUserInfo() {
private async platformUserCredentials() {
const args = this.args;
const authenticatedClient = new AuthenticatedClient();
const platformClient = await authenticatedClient.getClient();
await platformClient.initialize();

return await platformClient.user.get();
const userInfo = await platformClient.user.get();
const apiKey = await authenticatedClient.createImpersonateApiKey(args.name);

return {userInfo, apiKey};
}

private get flags() {
const {flags} = this.parse(Angular);
return flags;
}

private get args() {
const {args} = this.parse(Angular);
return args;
}

private displayFeedbackAfterSuccess(name: string) {
this.log(`
To get started:
cd ${name}
npm run start
See package.json for other available commands.
Happy hacking !
`);
Expand Down
Loading

0 comments on commit 2a48f0d

Please sign in to comment.