Skip to content

Commit

Permalink
Merge pull request #6 from snyk-tech-services/feat/delete-projects
Browse files Browse the repository at this point in the history
feat: delete created projects to clean up tests
  • Loading branch information
lili2311 authored May 21, 2020
2 parents ffe6db2 + e531369 commit 8e0e216
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 85 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist
package-lock.json
yarn.lock
.eslintcache
local.*
3 changes: 3 additions & 0 deletions src/lib/get-snyk-host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function getSnykHost(): string {
return process.env.SNYK_HOST || 'https://snyk.io';
}
8 changes: 4 additions & 4 deletions src/lib/import/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import * as debugLib from 'debug';
import * as _ from 'lodash';
import { Target, FilePath, ImportTarget } from '../types';
import { getApiToken } from '../get-api-token';
import { getSnykHost } from '../get-snyk-host';

const debug = debugLib('snyk:import');
const SNYK_HOST = process.env.SNYK_HOST || 'https://snyk.io';
const debug = debugLib('snyk:api-import');

export async function importTarget(
orgId: string,
Expand All @@ -29,6 +29,7 @@ export async function importTarget(
target,
files,
};
const SNYK_HOST = getSnykHost();

const res = await needle(
'post',
Expand All @@ -44,7 +45,6 @@ export async function importTarget(
},
);
if (res.statusCode && res.statusCode !== 201) {
debug('ERROR:', res.body);
throw new Error(
'Expected a 201 response, instead received: ' + JSON.stringify(res.body),
);
Expand All @@ -65,7 +65,7 @@ export async function importTarget(
innerError?: string;
} = new Error('Could not complete API import');
err.innerError = error;
debug(`Could not complete API import: ${error}`);
debug(`Could not complete API import: ${error.message}`);
throw err;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './import';
export * from './poll-import';
export * from './project';
3 changes: 2 additions & 1 deletion src/lib/poll-import/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import * as _ from 'lodash';
import * as pMap from 'p-map';
import { PollImportResponse } from '../types';
import { getApiToken } from '../get-api-token';

const debug = debugLib('snyk:poll-import');
const MIN_RETRY_WAIT_TIME = 30000;
const MIN_RETRY_WAIT_TIME = 3000;
const MAX_RETRY_COUNT = 1000;

export async function pollImportUrl(
Expand Down
57 changes: 57 additions & 0 deletions src/lib/project/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'source-map-support/register';
import * as needle from 'needle';
import * as debugLib from 'debug';
import { getApiToken } from '../get-api-token';
import { getSnykHost } from '../get-snyk-host';
const debug = debugLib('snyk:api-import');

export async function deleteProjects(
orgId: string,
projects: string[],
): Promise<boolean> {
const apiToken = getApiToken();
if (!(orgId && projects)) {
throw new Error(
`Missing required parameters. Please ensure you have provided: orgId & projectIds.`,
);
}
debug('Deleting projectIds:', projects.join(', '));

try {
const SNYK_HOST = getSnykHost();
const body = {
projects,
};
const res = await needle(
'post',
`${SNYK_HOST}/api/v1/org/${orgId}/projects/bulk-delete`,
body,
{
json: true,
// eslint-disable-next-line @typescript-eslint/camelcase
read_timeout: 30000,
headers: {
'content-type': 'application/json',
Authorization: `token ${apiToken}`,
},
},
);
if (res.statusCode !== 200) {
throw new Error(
`Expected a 200 response, instead received statusCode: ${
res.statusCode
},
body: ${JSON.stringify(res.body)}`,
);
}
return res.body;
} catch (error) {
debug('Could not delete project:', error.message || error);
const err: {
message?: string | undefined;
innerError?: string;
} = new Error('Could not delete project');
err.innerError = error;
throw err;
}
}
2 changes: 0 additions & 2 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Url } from 'url';

export interface ImportTarget {
orgId: string;
integrationId: string;
Expand Down
187 changes: 109 additions & 78 deletions test/lib/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,96 +3,127 @@ import {
pollImportUrl,
importTargets,
pollImportUrls,
deleteProjects,
} from '../../src/lib';
import { Status } from '../../src/lib/types';
import { Status, Project } from '../../src/lib/types';
const ORG_ID = 'f0125d9b-271a-4b50-ad23-80e12575a1bf';
const GITHUB_INTEGRATION_ID = 'c4de291b-e083-4c43-a72c-113463e0d268';

// TODO: afterEach delete the new projects
test('Import & Poll a repo', async () => {
const { pollingUrl } = await importTarget(
'f0125d9b-271a-4b50-ad23-80e12575a1bf',
'c4de291b-e083-4c43-a72c-113463e0d268',
{
async function deleteTestProjects(
discoveredProjects: Project[],
): Promise<void> {
const projectIds: string[] = [];
discoveredProjects.forEach(async (project) => {
if (project.projectUrl) {
const projectId = project.projectUrl.split('/').slice(-1)[0];
projectIds.push(projectId);
}
});
await deleteProjects(ORG_ID, projectIds);
}

describe('Single target', () => {
const discoveredProjects: Project[] = [];
it('Import & poll a repo', async () => {
const { pollingUrl } = await importTarget(ORG_ID, GITHUB_INTEGRATION_ID, {
name: 'shallow-goof-policy',
owner: 'snyk-fixtures',
branch: 'master',
},
);
expect(pollingUrl).not.toBeNull();
const importLog = await pollImportUrl(pollingUrl);
expect(importLog).toMatchObject({
id: expect.any(String),
status: 'complete',
created: expect.any(String),
});
expect(importLog.logs.length > 1).toBeTruthy();
expect(importLog.logs[0]).toMatchObject({
name: 'snyk-fixtures/shallow-goof-policy',
created: expect.any(String),
status: 'complete',
projects: [
{
projectUrl: expect.any(String),
success: true,
targetFile: expect.any(String),
},
],
});
expect(pollingUrl).not.toBeNull();
const importLog = await pollImportUrl(pollingUrl);
expect(importLog).toMatchObject({
id: expect.any(String),
status: 'complete',
created: expect.any(String),
});
expect(importLog.logs.length > 1).toBeTruthy();
expect(importLog.logs[0]).toMatchObject({
name: expect.any(String),
created: expect.any(String),
status: 'complete',
projects: [
{
projectUrl: expect.any(String),
success: true,
targetFile: expect.any(String),
},
],
});
// cleanup
importLog.logs.forEach((log) => {
discoveredProjects.push(...log.projects);
});
}, 30000000);
afterAll(async () => {
await deleteTestProjects(discoveredProjects);
});
}, 30000000);
});

test('importTargets & pollImportUrls multiple repos', async () => {
const pollingUrls = await importTargets([
{
orgId: 'f0125d9b-271a-4b50-ad23-80e12575a1bf',
integrationId: 'c4de291b-e083-4c43-a72c-113463e0d268',
target: {
name: 'shallow-goof-policy',
owner: 'snyk-fixtures',
branch: 'master',
},
},
{
orgId: 'f0125d9b-271a-4b50-ad23-80e12575a1bf',
integrationId: 'c4de291b-e083-4c43-a72c-113463e0d268',
target: {
name: 'ruby-with-versions',
owner: 'snyk-fixtures',
branch: 'master',
describe('Multiple targets', () => {
const discoveredProjects: Project[] = [];
it('importTargets & pollImportUrls multiple repos', async () => {
const pollingUrls = await importTargets([
{
orgId: ORG_ID,
integrationId: GITHUB_INTEGRATION_ID,
target: {
name: 'shallow-goof-policy',
owner: 'snyk-fixtures',
branch: 'master',
},
},
},
{
orgId: 'f0125d9b-271a-4b50-ad23-80e12575a1bf',
integrationId: 'c4de291b-e083-4c43-a72c-113463e0d268',
target: {
name: 'composer-with-vulns',
owner: 'snyk-fixtures',
branch: 'master',
{
orgId: ORG_ID,
integrationId: GITHUB_INTEGRATION_ID,
target: {
name: 'ruby-with-versions',
owner: 'snyk-fixtures',
branch: 'master',
},
},
},
]);
expect(pollingUrls.length >= 1).toBeTruthy();
const importLog = await pollImportUrls(pollingUrls);
expect(importLog[0]).toMatchObject({
id: expect.any(String),
status: 'complete',
created: expect.any(String),
});
// at least one job successfully finished
expect(importLog[0].logs[0]).toMatchObject({
name: expect.any(String),
created: expect.any(String),
status: 'complete',
projects: [
{
projectUrl: expect.any(String),
success: true,
targetFile: expect.any(String),
orgId: ORG_ID,
integrationId: GITHUB_INTEGRATION_ID,
target: {
name: 'composer-with-vulns',
owner: 'snyk-fixtures',
branch: 'master',
},
},
],
]);
expect(pollingUrls.length >= 1).toBeTruthy();
const importLog = await pollImportUrls(pollingUrls);
expect(importLog[0]).toMatchObject({
id: expect.any(String),
status: 'complete',
created: expect.any(String),
});
// at least one job successfully finished
expect(importLog[0].logs[0]).toMatchObject({
name: expect.any(String),
created: expect.any(String),
status: 'complete',
projects: [
{
projectUrl: expect.any(String),
success: true,
targetFile: expect.any(String),
},
],
});
expect(
importLog[0].logs.every((job) => job.status === Status.COMPLETE),
).toBeTruthy();
// cleanup
importLog[0].logs.forEach((log) => {
discoveredProjects.push(...log.projects);
});
}, 30000000);
afterAll(async () => {
await deleteTestProjects(discoveredProjects);
});
expect(
importLog[0].logs.every((job) => job.status === Status.COMPLETE),
).toBeTruthy();
}, 30000000);
});

test.todo('Import & poll in one');
test.todo('Failed import 100%');
Expand Down

0 comments on commit 8e0e216

Please sign in to comment.