import path from 'path';
import type { SinonFakeTimers } from 'sinon';
import sinon, { useFakeTimers } from 'sinon';

import { generateVulnerabilityReport } from './generate-vulnerability-report';
import { withTempDir, importFixture } from '../../test/helpers';
import { expect } from 'chai';
import type { KnownSeverity } from '../vulnerability';
import { vulnerabilityToSnyk } from '../vulnerability';
import nock from 'nock';

async function runGenerateReport(
  structure: Record<string, string>,
  options: { failOn?: KnownSeverity; createJiraIssues?: boolean } = {}
) {
  return withTempDir(structure, async (tempDir) => {
    let output = '';

    const error = await generateVulnerabilityReport({
      dependencyFiles: [path.resolve(tempDir, 'dependencies.json')],
      snykReports: [path.resolve(tempDir, 'snyk-report.json')],
      snykPolicyPath:
        '.snyk' in structure ? path.resolve(tempDir, '.snyk') : undefined,
      failOn: options.failOn,
      createJiraIssues: options.createJiraIssues,
      printResult: (result) => {
        output = result;
      },
    }).catch((err) => Promise.resolve(err));

    return { error, output };
  });
}

describe('generate-vulnerability-report', function () {
  it('reports an empty table if nothing matches', async function () {
    const { error, output } = await runGenerateReport({
      '.snyk': '',
      'dependencies.json': JSON.stringify([{ name: 'pkg1', version: '1.0.0' }]),
      'snyk-report.json': JSON.stringify({ vulnerabilities: [{}] }),
    });

    expect(error).to.be.undefined;
    expect(output).to.equal(`## Vulnerabilities Report (0 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
`);
  });

  it('reports an entry table if a vulnerability applies', async function () {
    const { error, output } = await runGenerateReport({
      '.snyk': '',
      'dependencies.json': JSON.stringify([{ name: 'pkg1', version: '1.0.0' }]),
      'snyk-report.json': JSON.stringify({
        vulnerabilities: [
          vulnerabilityToSnyk({
            id: 'v1',
            cves: ['CVE-1'],
            origins: [],
            title: 'Vulnerability title',
            description: 'Vulnerability description',
            urls: [],
            severity: 'low',
            score: 0.1,
            vulnerableSemver: '<=2.0.0',
            fixedIn: ['2.0.0'],
            packageName: 'pkg1',
            packageVersion: '1.0.0',
          }),
        ],
      }),
    });

    expect(error).to.be.undefined;
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | 0.1 (Low) | 2.0.0 | - |
`);
  });

  it('sorts vulnerabilities by severity', async function () {
    const { error, output } = await runGenerateReport({
      '.snyk': '',
      'dependencies.json': JSON.stringify([
        { name: 'pkg1', version: '1.0.0' },
        { name: 'pkg1', version: '1.5.0' },
        { name: 'pkg2', version: '1.0.0' },
      ]),
      'snyk-report.json': JSON.stringify({
        vulnerabilities: [
          vulnerabilityToSnyk({
            id: 'v1',
            cves: ['CVE-1'],
            origins: [],
            title: 'Vulnerability title',
            description: 'Vulnerability description',
            urls: [],
            severity: 'low',
            score: 0.1,
            vulnerableSemver: '<=2.0.0',
            fixedIn: ['2.0.0'],
            packageName: 'pkg1',
            packageVersion: '1.0.0',
          }),
          vulnerabilityToSnyk({
            id: 'v2',
            cves: ['CVE-1'],
            origins: [],
            title: 'Vulnerability title',
            description: 'Vulnerability description',
            urls: [],
            severity: 'critical',
            score: 10,
            vulnerableSemver: '<=2.0.0',
            fixedIn: ['2.0.0'],
            packageName: 'pkg1',
            packageVersion: '1.5.0',
          }),
          vulnerabilityToSnyk({
            id: 'v1',
            cves: ['CVE-1'],
            origins: [],
            title: 'Vulnerability title',
            description: 'Vulnerability description',
            urls: [],
            severity: 'medium',
            score: 5,
            vulnerableSemver: '<=2.0.0',
            fixedIn: ['2.0.0'],
            packageName: 'pkg2',
            packageVersion: '1.0.0',
          }),
        ],
      }),
    });

    expect(error).to.be.undefined;
    expect(output).to.equal(`## Vulnerabilities Report (3 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.5.0 | v2 | 10 (Critical) | 2.0.0 | - |
| pkg2@1.0.0 | v1 | 5 (Medium) | 2.0.0 | - |
| pkg1@1.0.0 | v1 | 0.1 (Low) | 2.0.0 | - |
`);
  });

  it('fails if severity is >= failOn', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': '',
        'dependencies.json': JSON.stringify([
          { name: 'pkg1', version: '1.0.0' },
        ]),
        'snyk-report.json': JSON.stringify({
          vulnerabilities: [
            vulnerabilityToSnyk({
              id: 'v1',
              cves: ['CVE-1'],
              origins: [],
              title: 'Vulnerability title',
              description: 'Vulnerability description',
              urls: [],
              severity: 'low',
              score: 0.1,
              vulnerableSemver: '<=2.0.0',
              fixedIn: ['2.0.0'],
              packageName: 'pkg1',
              packageVersion: '1.0.0',
            }),
          ],
        }),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.equal(
      'Vulnerabilities check failed: found vulnerabilities >= "low"'
    );
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | 0.1 (Low) | 2.0.0 | - |
`);
  });

  it('fails if severity unknown', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': '',
        'dependencies.json': JSON.stringify([
          { name: 'pkg1', version: '1.0.0' },
        ]),
        'snyk-report.json': JSON.stringify({
          vulnerabilities: [
            vulnerabilityToSnyk({
              id: 'v1',
              cves: ['CVE-1'],
              origins: [],
              title: 'Vulnerability title',
              description: 'Vulnerability description',
              urls: [],
              severity: 'unknown',
              score: undefined,
              vulnerableSemver: '<=2.0.0',
              fixedIn: ['2.0.0'],
              packageName: 'pkg1',
              packageVersion: '1.0.0',
            }),
          ],
        }),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.equal(
      'Vulnerabilities check failed: found vulnerabilities >= "low"'
    );
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | ? (Unknown) | 2.0.0 | - |
`);
  });

  it('auto-ignores vulnerabilities without remediation', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': '',
        'dependencies.json': JSON.stringify([
          { name: 'pkg1', version: '1.0.0' },
        ]),
        'snyk-report.json': JSON.stringify({
          vulnerabilities: [
            vulnerabilityToSnyk({
              id: 'v1',
              cves: ['CVE-1'],
              origins: [],
              title: 'Vulnerability title',
              description: 'Vulnerability description',
              urls: [],
              severity: 'critical',
              score: 10,
              vulnerableSemver: '<=2.0.0',
              fixedIn: [],
              packageName: 'pkg1',
              packageVersion: '1.0.0',
            }),
          ],
        }),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.be.undefined;
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | 10 (Critical) | N/A | Reason: Remediation not available yet |
`);
  });

  it('allows to ignore vulnerabilities with a snyk policy', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': `# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
  v1:
    - '*':
        reason: None Given
        expires: 3000-06-19T16:42:35.740Z
        created: 2023-05-20T16:42:35.746Z
patch: {}
`,
        'dependencies.json': JSON.stringify([
          { name: 'pkg1', version: '1.0.0' },
        ]),
        'snyk-report.json': JSON.stringify({
          vulnerabilities: [
            vulnerabilityToSnyk({
              id: 'v1',
              cves: ['CVE-1'],
              origins: [],
              title: 'Vulnerability title',
              description: 'Vulnerability description',
              urls: [],
              severity: 'critical',
              score: 10,
              vulnerableSemver: '<=2.0.0',
              fixedIn: ['2.0.0'],
              packageName: 'pkg1',
              packageVersion: '1.0.0',
            }),
          ],
        }),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.be.undefined;
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | 10 (Critical) | 2.0.0 | Reason: None Given |
`);
  });

  it('allows to ignore unknown severity', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': `# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
  v1:
    - '*':
        reason: None Given
        expires: 3000-06-19T16:42:35.740Z
        created: 2023-05-20T16:42:35.746Z
patch: {}
`,
        'dependencies.json': JSON.stringify([
          { name: 'pkg1', version: '1.0.0' },
        ]),
        'snyk-report.json': JSON.stringify({
          vulnerabilities: [
            vulnerabilityToSnyk({
              id: 'v1',
              cves: ['CVE-1'],
              origins: [],
              title: 'Vulnerability title',
              description: 'Vulnerability description',
              urls: [],
              severity: 'unknown',
              score: undefined,
              vulnerableSemver: '<=2.0.0',
              fixedIn: ['2.0.0'],
              packageName: 'pkg1',
              packageVersion: '1.0.0',
            }),
          ],
        }),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.be.undefined;
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | ? (Unknown) | 2.0.0 | Reason: None Given |
`);
  });

  it('fails with expired policies', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': `# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
  v1:
    - '*':
        reason: None Given
        expires: 2022-06-19T16:42:35.740Z
        created: 2022-05-20T16:42:35.746Z
patch: {}
`,
        'dependencies.json': JSON.stringify([
          { name: 'pkg1', version: '1.0.0' },
        ]),
        'snyk-report.json': JSON.stringify({
          vulnerabilities: [
            vulnerabilityToSnyk({
              id: 'v1',
              cves: ['CVE-1'],
              origins: [],
              title: 'Vulnerability title',
              description: 'Vulnerability description',
              urls: [],
              severity: 'critical',
              score: 10,
              vulnerableSemver: '<=2.0.0',
              fixedIn: ['2.0.0'],
              packageName: 'pkg1',
              packageVersion: '1.0.0',
            }),
          ],
        }),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.equal(
      'Vulnerabilities check failed: found vulnerabilities >= "low"'
    );
    expect(output).to.equal(`## Vulnerabilities Report (1 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| pkg1@1.0.0 | v1 | 10 (Critical) | 2.0.0 | Reason: None Given (Expired) |
`);
  });

  it('works with the result of scan-node-js', async function () {
    const { error, output } = await runGenerateReport(
      {
        '.snyk': '',
        'dependencies.json': JSON.stringify([
          { name: '.node.js', version: '16.19.1' },
        ]),
        'snyk-report.json': JSON.stringify(
          await importFixture('expect-unknown.json')
        ),
      },
      { failOn: 'low' }
    );

    expect(error?.message).to.equal(
      'Vulnerabilities check failed: found vulnerabilities >= "low"'
    );
    expect(output).to.equal(`## Vulnerabilities Report (3 vulnerabilities)
| dep@version | id | score | fixed in | ignored |
| ----------- | -- | ----- | -------- | ------- |
| .node.js@16.19.1 | NSWG-COR-99 | ? (Unknown) | ^14.20.0, ^16.20.0, ^18.9.1 | - |
| .node.js@16.19.1 | NSWG-COR-98 | 7.3 (High) | ^14.20.0, ^16.20.0, ^18.5.0 | - |
| .node.js@16.19.1 | NSWG-COR-95 | 6.5 (Medium) | ^14.20.0, ^16.20.0, ^18.5.0 | - |
`);
  });

  describe('createJiraIssues', function () {
    const envBackup = { ...process.env };

    const mockJiraSearchApi = (
      pkg: string,
      vulnId: string,
      { expired } = { expired: false }
    ) => {
      return nock('https://jira.example.com')
        .get('/rest/api/2/search')
        .matchHeader(
          'authorization',
          `Bearer ${process.env.JIRA_API_TOKEN ?? ''}`
        )
        .query({
          jql: `project="MY-PROJECT" AND issuetype="Build Failure" AND resolution=Unresolved AND summary~"Vulnerability ${vulnId} found on ${pkg}${
            expired ? ' (Policy Expired)' : ''
          }"`,
        });
    };

    const mockCreateIssueApi = (fn: (any) => void) => {
      return nock('https://jira.example.com')
        .post('/rest/api/2/issue/', (_body: any) => {
          fn(_body);
          return true;
        })
        .matchHeader(
          'authorization',
          `Bearer ${process.env.JIRA_API_TOKEN ?? ''}`
        );
    };

    let clock: SinonFakeTimers;

    beforeEach(function () {
      process.env.JIRA_BASE_URL = 'https://jira.example.com';
      process.env.JIRA_API_TOKEN = 'tok123';
      process.env.JIRA_PROJECT = 'MY-PROJECT';
      delete process.env.JIRA_VULNERABILITY_BUILD_INFO;
      clock = useFakeTimers(new Date('2023-01-01T12:00:00.468Z'));
    });

    afterEach(function () {
      clock.restore();
      [
        'JIRA_BASE_URL',
        'JIRA_API_TOKEN',
        'JIRA_PROJECT',
        'JIRA_VULNERABILITY_BUILD_INFO',
      ].forEach((k) => {
        if (k in envBackup) {
          process.env[k] = envBackup[k];
        } else {
          delete process.env[k];
        }
      });

      nock.cleanAll();
    });

    it('trows an error if there are missing env vars', async function () {
      process.env.JIRA_BASE_URL = '';

      const { error } = await runGenerateReport(
        {
          'dependencies.json': JSON.stringify([]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: [],
          }),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.equal(
        'Missing required variables to create Jira tickets: JIRA_BASE_URL'
      );
    });

    it('creates a ticket if does not exist', async function () {
      mockJiraSearchApi('pkg1@1.0.0', 'v1').reply(200, { total: 0 });

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      const { error } = await runGenerateReport(
        {
          '.snyk': '',
          'dependencies.json': JSON.stringify([
            { name: 'pkg1', version: '1.0.0' },
          ]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: [
              vulnerabilityToSnyk({
                id: 'v1',
                cves: ['CVE-1'],
                origins: ['x > y > z'],
                title: 'Vulnerability title',
                description: 'Vulnerability description',
                urls: [],
                severity: 'low',
                score: 0.1,
                vulnerableSemver: '<=2.0.0',
                fixedIn: ['2.0.0'],
                packageName: 'pkg1',
                packageVersion: '1.0.0',
              }),
            ],
          }),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.be.undefined;
      expect(createIssue).to.have.been.calledOnceWith({
        fields: {
          project: { key: 'MY-PROJECT' },
          summary: 'Vulnerability v1 found on pkg1@1.0.0',
          description: `h4. Vulnerability Details

- *Affected Package*: pkg1
- *Affected Version*: 1.0.0
- *Fixed In*: 2.0.0
- *Severity*: low
- *Cvss score*: 0.1

h4. Vulnerability Description

{panel:title=v1}
Vulnerability description
{panel}

h4. Vulnerable Paths

# {{x > y > z}}

h4. Links

- [v1|https://security.snyk.io/vuln/v1]
- [pkg1@1.0.0 vulnerabilities|https://security.snyk.io/package/npm/pkg1/1.0.0]
- [CVE-1|https://nvd.nist.gov/vuln/detail/CVE-1]
`,
          issuetype: { name: 'Build Failure' },
          components: [{ name: 'Vulnerability Management' }],
          priority: { name: 'Minor - P4' },
          duedate: '2023-03-26',
        },
      });
    });

    it('creates a ticket and truncates origins if > 10', async function () {
      mockJiraSearchApi('pkg1@1.0.0', 'v1').reply(200, { total: 0 });

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      const { error } = await runGenerateReport(
        {
          '.snyk': '',
          'dependencies.json': JSON.stringify([
            { name: 'pkg1', version: '1.0.0' },
          ]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: new Array(11).fill('').map((_, i) =>
              vulnerabilityToSnyk({
                id: 'v1',
                cves: ['CVE-1'],
                origins: [`path ${i + 1}`],
                title: 'Vulnerability title',
                description: 'Vulnerability description',
                urls: [],
                severity: 'low',
                score: 0.1,
                vulnerableSemver: '<=2.0.0',
                fixedIn: ['2.0.0'],
                packageName: 'pkg1',
                packageVersion: '1.0.0',
              })
            ),
          }),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.be.undefined;
      expect(createIssue).to.have.been.calledOnceWith({
        fields: {
          project: { key: 'MY-PROJECT' },
          summary: 'Vulnerability v1 found on pkg1@1.0.0',
          description: `h4. Vulnerability Details

- *Affected Package*: pkg1
- *Affected Version*: 1.0.0
- *Fixed In*: 2.0.0
- *Severity*: low
- *Cvss score*: 0.1

h4. Vulnerability Description

{panel:title=v1}
Vulnerability description
{panel}

h4. Vulnerable Paths

# {{path 1}}
# {{path 2}}
# {{path 3}}
# {{path 4}}
# {{path 5}}
# {{path 6}}
# {{path 7}}
# {{path 8}}
# {{path 9}}
# {{path 10}}
# and other 1 vulnerable path.

h4. Links

- [v1|https://security.snyk.io/vuln/v1]
- [pkg1@1.0.0 vulnerabilities|https://security.snyk.io/package/npm/pkg1/1.0.0]
- [CVE-1|https://nvd.nist.gov/vuln/detail/CVE-1]
`,
          issuetype: { name: 'Build Failure' },
          components: [{ name: 'Vulnerability Management' }],
          priority: { name: 'Minor - P4' },
          duedate: '2023-03-26',
        },
      });
    });

    it('creates a ticket with build info if present', async function () {
      mockJiraSearchApi('pkg1@1.0.0', 'v1').reply(200, { total: 0 });

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      process.env.JIRA_VULNERABILITY_BUILD_INFO = `- commit: ...`;
      const { error } = await runGenerateReport(
        {
          '.snyk': '',
          'dependencies.json': JSON.stringify([
            { name: 'pkg1', version: '1.0.0' },
          ]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: [
              vulnerabilityToSnyk({
                id: 'v1',
                cves: ['CVE-1'],
                origins: ['x > y > z'],
                title: 'Vulnerability title',
                description: 'Vulnerability description',
                urls: [],
                severity: 'low',
                score: 0.1,
                vulnerableSemver: '<=2.0.0',
                fixedIn: ['2.0.0'],
                packageName: 'pkg1',
                packageVersion: '1.0.0',
              }),
            ],
          }),
        },
        { createJiraIssues: true }
      );

      expect(createIssue).to.have.been.calledOnceWith({
        fields: {
          project: { key: 'MY-PROJECT' },
          summary: 'Vulnerability v1 found on pkg1@1.0.0',
          description: `h4. Vulnerability Details

- *Affected Package*: pkg1
- *Affected Version*: 1.0.0
- *Fixed In*: 2.0.0
- *Severity*: low
- *Cvss score*: 0.1

h4. Vulnerability Description

{panel:title=v1}
Vulnerability description
{panel}

h4. Vulnerable Paths

# {{x > y > z}}

h4. Links

- [v1|https://security.snyk.io/vuln/v1]
- [pkg1@1.0.0 vulnerabilities|https://security.snyk.io/package/npm/pkg1/1.0.0]
- [CVE-1|https://nvd.nist.gov/vuln/detail/CVE-1]

h4. Build Info

- commit: ...
`,
          issuetype: { name: 'Build Failure' },
          components: [{ name: 'Vulnerability Management' }],
          priority: { name: 'Minor - P4' },
          duedate: '2023-03-26',
        },
      });

      expect(error?.message).to.be.undefined;
    });

    it('does not create a ticket if exists', async function () {
      mockJiraSearchApi('pkg1@1.0.0', 'v1').reply(200, { total: 1 });

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      const { error } = await runGenerateReport(
        {
          '.snyk': '',
          'dependencies.json': JSON.stringify([
            { name: 'pkg1', version: '1.0.0' },
          ]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: [
              vulnerabilityToSnyk({
                id: 'v1',
                cves: ['CVE-1'],
                origins: [],
                title: 'Vulnerability title',
                description: 'Vulnerability description',
                urls: [],
                severity: 'low',
                score: 0.1,
                vulnerableSemver: '<=2.0.0',
                fixedIn: ['2.0.0'],
                packageName: 'pkg1',
                packageVersion: '1.0.0',
              }),
            ],
          }),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.be.undefined;
      expect(createIssue).to.not.have.been.called;
    });

    it('does not create a ticket if the vulnerability is ignored', async function () {
      mockJiraSearchApi('pkg1@1.0.0', 'v1').reply(200, { total: 0 });

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      const { error } = await runGenerateReport(
        {
          '.snyk': `# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
  v1:
    - '*':
        reason: None Given
        expires: 3000-06-19T16:42:35.740Z
        created: 2023-05-20T16:42:35.746Z
patch: {}
`,
          'dependencies.json': JSON.stringify([
            { name: 'pkg1', version: '1.0.0' },
          ]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: [
              vulnerabilityToSnyk({
                id: 'v1',
                cves: ['CVE-1'],
                origins: [],
                title: 'Vulnerability title',
                description: 'Vulnerability description',
                urls: [],
                severity: 'low',
                score: 0.1,
                vulnerableSemver: '<=2.0.0',
                fixedIn: ['2.0.0'],
                packageName: 'pkg1',
                packageVersion: '1.0.0',
              }),
            ],
          }),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.be.undefined;
      expect(createIssue).to.not.have.been.called;
    });

    it('creates a policy expired ticket', async function () {
      mockJiraSearchApi('pkg1@1.0.0', 'v1', { expired: true }).reply(200, {
        total: 0,
      });

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      const { error } = await runGenerateReport(
        {
          '.snyk': `# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.25.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
  v1:
    - '*':
        reason: None Given
        expires: 2000-06-19T16:42:35.740Z
        created: 2023-05-20T16:42:35.746Z
patch: {}
`,
          'dependencies.json': JSON.stringify([
            { name: 'pkg1', version: '1.0.0' },
          ]),
          'snyk-report.json': JSON.stringify({
            vulnerabilities: [
              vulnerabilityToSnyk({
                id: 'v1',
                cves: ['CVE-1'],
                origins: [],
                title: 'Vulnerability title',
                description: 'Vulnerability description',
                urls: [],
                severity: 'low',
                score: 0.1,
                vulnerableSemver: '<=2.0.0',
                fixedIn: ['2.0.0'],
                packageName: 'pkg1',
                packageVersion: '1.0.0',
              }),
            ],
          }),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.be.undefined;
      expect(createIssue).to.have.been.calledOnceWith({
        fields: {
          project: { key: 'MY-PROJECT' },
          summary: 'Vulnerability v1 found on pkg1@1.0.0 (Policy Expired)',
          description: `h4. Vulnerability Details

- *Affected Package*: pkg1
- *Affected Version*: 1.0.0
- *Fixed In*: 2.0.0
- *Severity*: low
- *Cvss score*: 0.1

h4. Vulnerability Description

{panel:title=v1}
Vulnerability description
{panel}

h4. Vulnerable Paths

# {{pkg1@1.0.0}}

h4. Links

- [v1|https://security.snyk.io/vuln/v1]
- [pkg1@1.0.0 vulnerabilities|https://security.snyk.io/package/npm/pkg1/1.0.0]
- [CVE-1|https://nvd.nist.gov/vuln/detail/CVE-1]
`,
          issuetype: { name: 'Build Failure' },
          components: [{ name: 'Vulnerability Management' }],
          priority: { name: 'Minor - P4' },
          duedate: '2023-03-26',
        },
      });
    });

    it('works with a real example', async function () {
      mockJiraSearchApi('request@2.88.2', 'SNYK-JS-REQUEST-3361831').reply(
        200,
        {
          total: 0,
        }
      );

      const createIssue = sinon.spy();
      mockCreateIssueApi(createIssue).reply(200, { key: 'TICKET-123' });

      const { error } = await runGenerateReport(
        {
          'dependencies.json': JSON.stringify([
            { name: 'request', version: '2.88.2' },
          ]),
          'snyk-report.json': JSON.stringify(
            await importFixture('snyk-test.json')
          ),
        },
        { createJiraIssues: true }
      );

      expect(error?.message).to.be.undefined;
      expect(createIssue).to.have.been.calledOnceWith(
        await importFixture('expected-jira-ticket.json')
      );
    });
  });
});
