Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: migrate code tests to acceptance #5139

Merged
merged 7 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lib/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ if (!config.ROOT) {

config.PUBLIC_VULN_DB_URL = 'https://security.snyk.io';

config.CODE_CLIENT_PROXY_URL = process.env.SNYK_CODE_CLIENT_PROXY_URL || '';

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduce the ability to configure the CODE_CLIENT_PROXY_URL via environment variables. This is aimed at making it easier to test.

export default config;
192 changes: 192 additions & 0 deletions test/acceptance/deepcode-fake-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import * as express from 'express';
Copy link
Contributor

@j-luong j-luong Mar 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what's the reason for not extending fake-server.ts and creating a second "fake-server"?

edit: I think this comment may have something to do with it? If so, it might be worth adding something like a TODO in this file so we don't forget about it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good question, fake-server is less generic than it sounds, it aims to replicate our primary public APIs, whereas the second server is for secondary set of public APIs. Currently these are distributed across two "namespaces" api.snyk.io and deepcode.snyk.io. This is something of a legacy shadow, I agree something to tidy up in a future refactor. Ideally as part of moving our fake servers to assign their own port 🤩

import * as http from 'http';
import * as net from 'net';

export type FakeDeepCodeServer = {
getRequests: () => express.Request[];
popRequest: () => express.Request;
popRequests: (num: number) => express.Request[];
setCustomResponse: (next: Record<string, unknown>) => void;
setFiltersResponse: (next: Record<string, unknown>) => void;
setNextResponse: (r: any) => void;
setNextStatusCode: (code: number) => void;
setSarifResponse: (r: any) => void;
listen: (callback: () => void) => void;
restore: () => void;
close: (callback: () => void) => void;
getPort: () => number;
};

export const fakeDeepCodeServer = (): FakeDeepCodeServer => {
let filtersResponse: Record<string, unknown> | null = {
configFiles: [],
extensions: ['.java'],
};
let sarifResponse: Record<string, unknown> | null = null;
let requests: express.Request[] = [];
// the status code to return for the next request, overriding statusCode
let nextResponse: Record<string, unknown> | undefined = undefined;
let nextStatusCode: number | undefined = undefined;
let customResponse: Record<string, unknown> | undefined = undefined;
let server: http.Server | undefined = undefined;
const sockets = new Set();

const restore = () => {
requests = [];
customResponse = undefined;
nextResponse = undefined;
nextStatusCode = undefined;
sarifResponse = null;
filtersResponse = { configFiles: [], extensions: ['.java', '.js'] };
};

const getRequests = () => {
return requests;
};

const popRequest = () => {
return requests.pop()!;
};

const popRequests = (num: number) => {
return requests.splice(requests.length - num, num);
};

const setCustomResponse = (next: typeof customResponse) => {
customResponse = next;
};

const setFiltersResponse = (response: string | Record<string, unknown>) => {
if (typeof response === 'string') {
filtersResponse = JSON.parse(response);
return;
}
filtersResponse = response;
};

const setNextResponse = (response: string | Record<string, unknown>) => {
if (typeof response === 'string') {
nextResponse = JSON.parse(response);
return;
}
nextResponse = response;
};

const setNextStatusCode = (code: number) => {
nextStatusCode = code;
};

const setSarifResponse = (response: string | Record<string, unknown>) => {
if (typeof response === 'string') {
sarifResponse = JSON.parse(response);
return;
}
sarifResponse = response;
};

const app = express();
app.use((req, res, next) => {
requests.push(req);
next();
});

app.use((req, res, next) => {
if (nextStatusCode) {
const code = nextStatusCode;
res.status(code);
}

if (nextResponse) {
const response = nextResponse;
res.send(response);
return;
}
next();
});

app.get('/filters', (req, res) => {
res.status(200);
res.send(filtersResponse);
});

app.post('/bundle', (req, res) => {
res.status(200);

res.send({
bundleHash: 'bundle-hash',
missingFiles: [],
});
});

app.post('/analysis', (req, res) => {
res.status(200);
res.send({
timing: {
fetchingCode: 1,
analysis: 1,
queue: 1,
},
coverage: [],
status: 'COMPLETE',
type: 'sarif',
sarif: sarifResponse,
});
});

const listenPromise = () => {
return new Promise<void>((resolve) => {
server = http.createServer(app).listen(resolve);

server?.on('connection', (socket) => {
sockets.add(socket);
});
});
};

const listen = (callback: () => void) => {
listenPromise().then(callback);
};

const closePromise = () => {
return new Promise<void>((resolve) => {
if (!server) {
resolve();
return;
}
server.close(() => resolve());
server = undefined;
});
};

const close = (callback: () => void) => {
for (const socket of sockets) {
(socket as net.Socket)?.destroy();
sockets.delete(socket);
}

closePromise().then(callback);
};

const getPort = () => {
const address = server?.address();
if (address && typeof address === 'object') {
return address.port;
}
throw new Error('port not found');
};

return {
getRequests,
popRequest,
popRequests,
setCustomResponse: setCustomResponse,
setFiltersResponse,
setSarifResponse,
setNextResponse,
setNextStatusCode,
listen,
restore,
close,
getPort,
};
};
58 changes: 58 additions & 0 deletions test/acceptance/fake-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export type FakeServer = {
setNextStatusCode: (c: number) => void;
setStatusCode: (c: number) => void;
setStatusCodes: (c: number[]) => void;
setLocalCodeEngineConfiguration: (next: Record<string, unknown>) => void;
setFeatureFlag: (featureFlag: string, enabled: boolean) => void;
setOrgSetting: (setting: string, enabled: boolean) => void;
unauthorizeAction: (action: string, reason?: string) => void;
listen: (port: string | number, callback: () => void) => void;
listenPromise: (port: string | number) => Promise<void>;
Expand All @@ -59,6 +61,10 @@ export type FakeServer = {
export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
let requests: express.Request[] = [];
let featureFlags: Map<string, boolean> = featureFlagDefaults();
let availableSettings: Map<string, boolean> = new Map();
let localCodeEngineConfiguration: Record<string, unknown> = {
enabled: false,
};
let unauthorizedActions = new Map();
// the status code to return for the next request, overriding statusCode
let nextStatusCode: number | undefined = undefined;
Expand All @@ -75,6 +81,7 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
requests = [];
customResponse = undefined;
featureFlags = featureFlagDefaults();
availableSettings = new Map();
unauthorizedActions = new Map();
};

Expand All @@ -94,6 +101,16 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
customResponse = next;
};

const setLocalCodeEngineConfiguration = (
response: string | Record<string, unknown>,
) => {
if (typeof response === 'string') {
localCodeEngineConfiguration = JSON.parse(response);
return;
}
localCodeEngineConfiguration = response;
};

const setNextResponse = (response: string | Record<string, unknown>) => {
if (typeof response === 'string') {
nextResponse = JSON.parse(response);
Expand All @@ -118,6 +135,10 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
featureFlags.set(featureFlag, enabled);
};

const setOrgSetting = (setting: string, enabled: boolean) => {
availableSettings.set(setting, enabled);
};

const unauthorizeAction = (
action: string,
reason = 'unauthorized by test',
Expand Down Expand Up @@ -388,6 +409,41 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
});
});

app.get(basePath + '/cli-config/settings/:setting', (req, res) => {
const org = req.query.org;
const setting = req.params.setting;
if (org === 'no-flag') {
res.send({
ok: false,
userMessage: `Org ${org} doesn't have '${setting}' feature enabled'`,
});
return;
}

if (availableSettings.has(setting)) {
const settingEnabled = availableSettings.get(setting);
// TODO: Refactor to support passing in an org setting with additional
// properties, e.g. localCodeEngine.
if (settingEnabled && setting === 'sast') {
return res.send({
ok: true,
sastEnabled: true,
localCodeEngine: localCodeEngineConfiguration,
});
}

return res.send({
ok: false,
userMessage: `Org ${org} doesn't have '${setting}' feature enabled'`,
});
}

// default: return false for all feature flags
res.send({
ok: false,
});
});

app.get(basePath + '/cli-config/feature-flags/:featureFlag', (req, res) => {
const org = req.query.org;
const flag = req.params.featureFlag;
Expand Down Expand Up @@ -709,11 +765,13 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
popRequest,
popRequests,
setCustomResponse: setCustomResponse,
setLocalCodeEngineConfiguration,
setNextResponse,
setNextStatusCode,
setStatusCode,
setStatusCodes,
setFeatureFlag,
setOrgSetting,
unauthorizeAction,
listen,
listenPromise,
Expand Down
31 changes: 31 additions & 0 deletions test/fixtures/sast-empty/empty-sarif.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "SnykCode",
"semanticVersion": "1.0.0",
"version": "1.0.0",
"rules": []
}
},
"results": [],
"properties": {
"coverage": [
{
"files": 8,
"isSupported": true,
"lang": "JavaScript"
},
{
"files": 1,
"isSupported": true,
"lang": "HTML"
}
]
}
}
]
}
Empty file.
1 change: 1 addition & 0 deletions test/fixtures/sast-empty/shallow_empty/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('shallow_empty');
31 changes: 31 additions & 0 deletions test/fixtures/sast/empty-sarif.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "SnykCode",
"semanticVersion": "1.0.0",
"version": "1.0.0",
"rules": []
}
},
"results": [],
"properties": {
"coverage": [
{
"files": 8,
"isSupported": true,
"lang": "JavaScript"
},
{
"files": 1,
"isSupported": true,
"lang": "HTML"
}
]
}
}
]
}
Loading
Loading