diff --git a/cliv2/go.mod b/cliv2/go.mod index b0aef5ee12..0ce9545686 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -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 diff --git a/cliv2/go.sum b/cliv2/go.sum index 0ed04f0d72..5437d181e6 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -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= diff --git a/test/acceptance/fake-server.ts b/test/acceptance/fake-server.ts index 3b8ccb62b9..f9214a1346 100644 --- a/test/acceptance/fake-server.ts +++ b/test/acceptance/fake-server.ts @@ -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({ diff --git a/test/jest/acceptance/snyk-aibom/aibom.spec.ts b/test/jest/acceptance/snyk-aibom/aibom.spec.ts index 7e6b5a7ce5..9c3302ec66 100644 --- a/test/jest/acceptance/snyk-aibom/aibom.spec.ts +++ b/test/jest/acceptance/snyk-aibom/aibom.spec.ts @@ -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); @@ -22,6 +18,12 @@ 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; @@ -29,7 +31,6 @@ function aiBomRestEndpointRequests(requests: Request[]): string[] { describe('snyk aibom (mocked servers only)', () => { let server: ReturnType; - let deepCodeServer: ReturnType; let envWithoutAuth: Record; let env: Record; const port = getServerPort(process); @@ -54,21 +55,12 @@ 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((resolve, reject) => { try { let serversReady = 0; - const totalServers = 2; + const totalServers = 1; const checkAndResolve = () => { serversReady++; if (serversReady === totalServers) { @@ -76,8 +68,6 @@ describe('snyk aibom (mocked servers only)', () => { } }; - deepCodeServer = fakeDeepCodeServer(); - deepCodeServer.listen(checkAndResolve); server = fakeServer(baseApi, 'snykToken'); server.listen(port, checkAndResolve); } catch (error) { @@ -86,37 +76,23 @@ describe('snyk aibom (mocked servers only)', () => { }); }); + afterAll(() => { + return new Promise((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((resolve) => { - deepCodeServer.close(() => { - server.close(() => { - resolve(); - }); - }); - }); }); test('`aibom` generates an AI-BOM CycloneDX with components', async () => { @@ -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', @@ -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', @@ -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 () => { - 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`, @@ -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`,