Skip to content
Open
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: 1 addition & 1 deletion cliv2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/google/uuid v1.6.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.34.0
github.com/snyk/cli-extension-ai-bom v0.0.0-20260127153523-c10ebb791f73
github.com/snyk/cli-extension-ai-bom v0.0.0-20260203141224-136b67ed6871
github.com/snyk/cli-extension-dep-graph v0.15.1
github.com/snyk/cli-extension-iac v0.0.0-20260115084339-e0c36e4dffdf
github.com/snyk/cli-extension-iac-rules v0.0.0-20260115114457-a8ac3358ec57
Expand Down
4 changes: 2 additions & 2 deletions cliv2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/snyk/cli-extension-ai-bom v0.0.0-20260127153523-c10ebb791f73 h1:OJ5oJ+BkEIDqTD9f8FngUnbFRRmnnF09Col09MvNLGQ=
github.com/snyk/cli-extension-ai-bom v0.0.0-20260127153523-c10ebb791f73/go.mod h1:1BduesFhzYzVa3IwWkynWHZLw97NVcimxNhuAq1sLgY=
github.com/snyk/cli-extension-ai-bom v0.0.0-20260203141224-136b67ed6871 h1:KLahQCcC04pRSz28SEWq9NUjzxz7/tp9hZi1l/Xntsg=
github.com/snyk/cli-extension-ai-bom v0.0.0-20260203141224-136b67ed6871/go.mod h1:eIq61+KliPjLwhaAZT87FfeyfK/4mJaGP0wqyFtf8pQ=
github.com/snyk/cli-extension-dep-graph v0.15.1 h1:SK1cMIfIzpmQhcfVnn77FZHQgcXT/3d9ZzTog1uPT3c=
github.com/snyk/cli-extension-dep-graph v0.15.1/go.mod h1:Do/xNThRKSbZcIC2JCCgkBJ2X/h/YbN5i12znPEEvjY=
github.com/snyk/cli-extension-iac v0.0.0-20260115084339-e0c36e4dffdf h1:43ITDrUkpPC0Hy/CvPsuYQVdZVFoaol5CNK253YdS6A=
Expand Down
53 changes: 53 additions & 0 deletions test/acceptance/fake-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,59 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
});
});

app.post(`/api/hidden/orgs/:orgId/upload_revisions`, (req, res) => {
res.status(201).send({
data: {
attributes: {
revision_type: 'snapshot',
sealed: false,
},
id: 'bc0729a7-109f-4fe9-a048-aac410e28c9a',
type: 'upload_revision',
},
jsonapi: {
version: '1.0',
},
links: {
self: {
href: '/orgs/bb262a15-d798-458b-81fa-30a92cb3475c/upload_revisions/bc0729a7-109f-4fe9-a048-aac410e28c9a',
},
},
});
});

app.post(
`/api/hidden/orgs/:orgId/upload_revisions/:uploadRevisionId/files`,
(_, res) => {
res.status(204);
res.send();
},
);

app.patch(
`/api/hidden/orgs/:orgId/upload_revisions/:uploadRevisionId`,
(req, res) => {
res.status(200).send({
data: {
attributes: {
revision_type: 'snapshot',
sealed: true,
},
id: 'fbdb5cc0-6e34-4191-b088-0dff740faf38',
type: 'upload_revision',
},
jsonapi: {
version: '1.0',
},
links: {
self: {
href: '/orgs/bb262a15-d798-458b-81fa-30a92cb3475c/upload_revisions/fbdb5cc0-6e34-4191-b088-0dff740faf38',
},
},
});
},
);

app.get(`/api/rest/orgs/:orgId/ai_bom_jobs/:jobId`, (req, res) => {
res.status(303);
res.send({
Expand Down
129 changes: 21 additions & 108 deletions test/jest/acceptance/snyk-aibom/aibom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import {
} from '../../../acceptance/fake-server';
import { getServerPort } from '../../util/getServerPort';
import { resolve } from 'path';
import { readFileSync } from 'fs';
import { runCommand } from '../../util/runCommand';

import { fakeDeepCodeServer } from '../../../acceptance/deepcode-fake-server';

jest.setTimeout(1000 * 60 * 5);

Expand All @@ -22,14 +18,19 @@ function aiBomRestEndpointRequests(requests: Request[]): string[] {
res.push(`${request.method}:/ai_boms`);
} else if (request.url.includes('/ai_bom_jobs')) {
res.push(`${request.method}:/ai_bom_jobs`);
} else if (request.url.match(/.*\/upload_revisions\/.*\/files/)) {
res.push(`${request.method}:/upload_revisions/:uploadRevisionId/files`);
} else if (request.url.match(/.*\/upload_revisions\/.*/)) {
res.push(`${request.method}:/upload_revisions/:uploadRevisionId`);
} else if (request.url.match(/.*\/upload_revisions/)) {
res.push(`${request.method}:/upload_revisions`);
}
}
return res;
}

describe('snyk aibom (mocked servers only)', () => {
let server: ReturnType<typeof fakeServer>;
let deepCodeServer: ReturnType<typeof fakeDeepCodeServer>;
let envWithoutAuth: Record<string, string>;
let env: Record<string, string>;
const port = getServerPort(process);
Expand All @@ -54,30 +55,19 @@ describe('snyk aibom (mocked servers only)', () => {
projectRoot,
'test/fixtures/ai-bom/python-chatbot',
);
const pythonRequirementsProject = resolve(
projectRoot,
'test/fixtures/ai-bom/requirements',
);

const notSupportedProject = resolve(
projectRoot,
'test/fixtures/ai-bom/not-supported',
);

beforeAll(() => {
return new Promise<void>((resolve, reject) => {
try {
let serversReady = 0;
const totalServers = 2;
const totalServers = 1;
const checkAndResolve = () => {
serversReady++;
if (serversReady === totalServers) {
resolve();
}
};

deepCodeServer = fakeDeepCodeServer();
deepCodeServer.listen(checkAndResolve);
server = fakeServer(baseApi, 'snykToken');
server.listen(port, checkAndResolve);
} catch (error) {
Expand All @@ -86,37 +76,23 @@ describe('snyk aibom (mocked servers only)', () => {
});
});

afterAll(() => {
return new Promise<void>((resolve) => {
server.close(() => {
resolve();
});
});
});

beforeEach(() => {
jest.resetAllMocks();
server.restore();
deepCodeServer.restore();
env = {
...initialEnvVars,
SNYK_CODE_CLIENT_PROXY_URL: `http://${ipAddress}:${deepCodeServer.getPort()}`,
};
envWithoutAuth = {
...initialEnvVarsWithoutAuth,
SNYK_CODE_CLIENT_PROXY_URL: `http://${ipAddress}:${deepCodeServer.getPort()}`,
};
deepCodeServer.setFiltersResponse({
configFiles: [],
extensions: ['.py', '.snykdepgraph'],
autofixExtensions: [],
});
const sarifPayload = readFileSync(
`${projectRoot}/test/fixtures/ai-bom/sample-ai-bom-sarif.json`,
).toString();
deepCodeServer.setSarifResponse(sarifPayload);
});

afterAll(() => {
return new Promise<void>((resolve) => {
deepCodeServer.close(() => {
server.close(() => {
resolve();
});
});
});
});

test('`aibom` generates an AI-BOM CycloneDX with components', async () => {
Expand All @@ -133,14 +109,12 @@ describe('snyk aibom (mocked servers only)', () => {
bom = JSON.parse(stdout);
}).not.toThrow();

const deeproxyRequestUrls = deepCodeServer
.getRequests()
.map((req) => `${req.method}:${req.url}`);
expect(deeproxyRequestUrls).toEqual(['GET:/filters', 'POST:/bundle']);

const aiBomRequests = aiBomRestEndpointRequests(server.getRequests());
expect(aiBomRequests).toEqual([
'POST:/ai_boms',
'POST:/upload_revisions',
'POST:/upload_revisions/:uploadRevisionId/files',
'PATCH:/upload_revisions/:uploadRevisionId',
'POST:/ai_boms',
'GET:/ai_bom_jobs',
'GET:/ai_boms',
Expand Down Expand Up @@ -168,14 +142,12 @@ describe('snyk aibom (mocked servers only)', () => {
bom = JSON.parse(stdout);
}).not.toThrow();

const deeproxyRequestUrls = deepCodeServer
.getRequests()
.map((req) => `${req.method}:${req.url}`);
expect(deeproxyRequestUrls).toEqual(['GET:/filters', 'POST:/bundle']);

const aiBomRequests = aiBomRestEndpointRequests(server.getRequests());
expect(aiBomRequests).toEqual([
'POST:/ai_boms',
'POST:/upload_revisions',
'POST:/upload_revisions/:uploadRevisionId/files',
'PATCH:/upload_revisions/:uploadRevisionId',
'POST:/ai_boms/upload',
'GET:/ai_bom_jobs',
'GET:/ai_boms',
Expand All @@ -200,60 +172,12 @@ describe('snyk aibom (mocked servers only)', () => {
);
expect(code).toEqual(2);

const deeproxyRequestUrls = deepCodeServer
.getRequests()
.map((req) => `${req.method}:${req.url}`);
expect(deeproxyRequestUrls).toEqual([]);

const aiBomRequests = aiBomRestEndpointRequests(server.getRequests());
expect(aiBomRequests).toEqual(['POST:/ai_boms']);

expect(stdout).toContain('unexpected status code 404 for CreateAIBOM');
});

test('`aibom` adds the depgraph to the bundle', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: The description of the PR doesn't really explain why these tests are removed? Could you please help me understand this?

const pipResult = await runCommand(
'pip',
['install', '-r', 'requirements.txt'],
{
shell: true,
cwd: pythonRequirementsProject,
},
);

expect(pipResult.code).toBe(0);

await runSnykCLI(`aibom ${pythonRequirementsProject} --experimental`, {
env,
});
const deeproxyRequestUrls = deepCodeServer
.getRequests()
.map((req) => `${req.method}:${req.url}`);
expect(deeproxyRequestUrls).toEqual([
'GET:/filters',
'POST:/bundle',
'PUT:/bundle/bundle-hash',
]);

const deepcodeBundleRequest = deepCodeServer.getRequests()[2];
expect(deepcodeBundleRequest.url).toEqual('/bundle/bundle-hash');

const deepcodeBundleRequestBody = JSON.parse(
Buffer.from(deepcodeBundleRequest.body.toString(), 'base64').toString(),
);
expect(deepcodeBundleRequestBody['files']).toBeDefined();
const fileNames = Object.keys(deepcodeBundleRequestBody['files']);
expect(fileNames.length).toEqual(1);
const depgraphFileName = fileNames[0];
expect(depgraphFileName.endsWith('.snykdepgraph')).toBe(true);
const depgraph = JSON.parse(
deepcodeBundleRequestBody['files'][depgraphFileName].content,
);

const packagesInDepgraph = depgraph.pkgs.map((pkg) => pkg.info.name);
expect(packagesInDepgraph).toContain('anthropic');
});

test('`aibom` generates an AI-BOM CycloneDX in the HTML format', async () => {
const { code, stdout } = await runSnykCLI(
`aibom ${pythonChatbotProject} --experimental --html`,
Expand Down Expand Up @@ -310,17 +234,6 @@ describe('snyk aibom (mocked servers only)', () => {
expect(stdout).toContain('Forbidden (SNYK-AIBOM-0002)');
});

test('handles an unsupported project', async () => {
const { code, stdout } = await runSnykCLI(
`aibom ${notSupportedProject} --experimental`,
{
env,
},
);
expect(code).toEqual(3);
expect(stdout).toContain('No supported files (SNYK-AIBOM-0003)');
});

test('handles no SNYK_TOKEN', async () => {
const { code, stdout } = await runSnykCLI(
`aibom ${pythonChatbotProject} --experimental`,
Expand Down