/*
 * This file is part of Dependency-Track.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) Steve Springett. All Rights Reserved.
 */
package org.dependencytrack.resources.v1;

import alpine.common.util.UuidUtil;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import org.dependencytrack.ResourceTest;
import org.dependencytrack.model.AffectedVersionAttribution;
import org.dependencytrack.model.AnalysisJustification;
import org.dependencytrack.model.AnalysisResponse;
import org.dependencytrack.model.AnalysisState;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Severity;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.model.VulnerableSoftware;
import org.dependencytrack.persistence.CweImporter;
import org.dependencytrack.tasks.scanners.AnalyzerIdentity;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.junit.Assert;
import org.junit.Test;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.UUID;

public class VulnerabilityResourceTest extends ResourceTest {

    @Override
    protected DeploymentContext configureDeployment() {
        return ServletDeploymentContext.forServlet(new ServletContainer(
                new ResourceConfig(VulnerabilityResource.class)
                        .register(ApiFilter.class)
                        .register(AuthenticationFilter.class)))
                .build();
    }

    @Test
    public void getVulnerabilitiesByComponentUuidTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/component/" + sampleData.c1.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertEquals(String.valueOf(2), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray json = parseJsonArray(response);
        Assert.assertNotNull(json);
        Assert.assertEquals(2, json.size());
        Assert.assertEquals("INT-1", json.getJsonObject(0).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(0).getString("source"));
        Assert.assertEquals("Description 1", json.getJsonObject(0).getString("description"));
        Assert.assertEquals("CRITICAL", json.getJsonObject(0).getString("severity"));
        Assert.assertNull(json.getJsonObject(0).getJsonObject("cwe"));
        Assert.assertNull(json.getJsonObject(0).getJsonArray("cwes"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(0).getString("uuid")));
        Assert.assertEquals("INT-2", json.getJsonObject(1).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(1).getString("source"));
        Assert.assertEquals("Description 2", json.getJsonObject(1).getString("description"));
        Assert.assertEquals("HIGH", json.getJsonObject(1).getString("severity"));
        Assert.assertNull(json.getJsonObject(1).getJsonObject("cwe"));
        Assert.assertNull(json.getJsonObject(1).getJsonArray("cwes"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(1).getString("uuid")));
    }

    @Test
    public void getVulnerabilitiesByComponentInvalidTest() throws Exception  {
        new SampleData();
        Response response = target(V1_VULNERABILITY + "/component/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The component could not be found.", body);
    }

    @Test
    public void getVulnerabilitiesByComponentUuidIncludeSuppressedTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/component/" + sampleData.c1.getUuid().toString())
                .queryParam("suppressed", "true")
                .request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertEquals(String.valueOf(3), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray json = parseJsonArray(response);
        Assert.assertNotNull(json);
        Assert.assertEquals(3, json.size());
        Assert.assertEquals("INT-1", json.getJsonObject(0).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(0).getString("source"));
        Assert.assertEquals("Description 1", json.getJsonObject(0).getString("description"));
        Assert.assertEquals("CRITICAL", json.getJsonObject(0).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(0).getString("uuid")));
        Assert.assertEquals("INT-2", json.getJsonObject(1).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(1).getString("source"));
        Assert.assertEquals("Description 2", json.getJsonObject(1).getString("description"));
        Assert.assertEquals("HIGH", json.getJsonObject(1).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(1).getString("uuid")));
        Assert.assertEquals("INT-3", json.getJsonObject(2).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(2).getString("source"));
        Assert.assertEquals("Description 3", json.getJsonObject(2).getString("description"));
        Assert.assertEquals("MEDIUM", json.getJsonObject(2).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(2).getString("uuid")));
    }

    @Test
    public void getVulnerabilitiesByProjectTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/project/" + sampleData.p1.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertEquals(String.valueOf(4), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray json = parseJsonArray(response);
        Assert.assertNotNull(json);
        Assert.assertEquals(4, json.size());
        Assert.assertEquals("INT-1", json.getJsonObject(0).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(0).getString("source"));
        Assert.assertEquals("Description 1", json.getJsonObject(0).getString("description"));
        Assert.assertEquals("CRITICAL", json.getJsonObject(0).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(0).getString("uuid")));
        Assert.assertEquals("INT-2", json.getJsonObject(1).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(1).getString("source"));
        Assert.assertEquals("Description 2", json.getJsonObject(1).getString("description"));
        Assert.assertEquals("HIGH", json.getJsonObject(1).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(1).getString("uuid")));
        Assert.assertEquals("INT-4", json.getJsonObject(2).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(2).getString("source"));
        Assert.assertEquals("Description 4", json.getJsonObject(2).getString("description"));
        Assert.assertEquals("LOW", json.getJsonObject(2).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(2).getString("uuid")));
        Assert.assertEquals("INT-5", json.getJsonObject(3).getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getJsonObject(3).getString("source"));
        Assert.assertEquals("Description 5", json.getJsonObject(3).getString("description"));
        Assert.assertEquals("CRITICAL", json.getJsonObject(3).getString("severity"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getJsonObject(3).getString("uuid")));
    }

    @Test
    public void getVulnerabilitiesByProjectIncludeProjectSuppressedTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/project/" + sampleData.p2.getUuid().toString())
                .queryParam("suppressed", "true")
                .request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertEquals(String.valueOf(2), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray json = parseJsonArray(response);
        Assert.assertNotNull(json);
        Assert.assertEquals(2, json.size());
        Assert.assertEquals("INT-4", json.getJsonObject(0).getString("vulnId"));
        Assert.assertEquals("INT-5", json.getJsonObject(1).getString("vulnId"));
    }

    @Test
    public void getVulnerabilitiesByProjectInvalidTest() throws Exception {
        new SampleData();
        Response response = target(V1_VULNERABILITY + "/project/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The project could not be found.", body);
    }

    @Test
    public void getVulnerabilityByUuidTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/" + sampleData.v1.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonObject json = parseJsonObject(response);
        Assert.assertNotNull(json);
        Assert.assertEquals("INT-1", json.getString("vulnId"));
    }

    @Test
    public void getVulnerabilityByUuidInvalidTest() throws Exception {
        new SampleData();
        Response response = target(V1_VULNERABILITY + "/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void getVulnerabilityByVulnIdTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/source/" + sampleData.v1.getSource() + "/vuln/" + sampleData.v1.getVulnId()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonObject json = parseJsonObject(response);
        Assert.assertNotNull(json);
        Assert.assertEquals("INT-1", json.getString("vulnId"));
        JsonArray affectedComponents = json.getJsonArray("affectedComponents");
        Assert.assertNotNull(affectedComponents);
        JsonArray affectedVersionAttributions = affectedComponents.getJsonObject(0).getJsonArray("affectedVersionAttributions");
        Assert.assertNotNull(affectedVersionAttributions);
        Assert.assertEquals(affectedVersionAttributions.getJsonObject(0).getString("source"), "INTERNAL");
    }

    @Test
    public void getVulnerabilityByVulnIdInvalidTest() throws Exception {
        new SampleData();
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/blah").request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void getAffectedProjectTest() throws Exception {
        SampleData sampleData = new SampleData();
        Response response = target(V1_VULNERABILITY + "/source/" + sampleData.v1.getSource() + "/vuln/" + sampleData.v1.getVulnId() + "/projects").request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertEquals(String.valueOf(1), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray json = parseJsonArray(response);
        Assert.assertNotNull(json);
        Assert.assertEquals("Project 1", json.getJsonObject(0).getString("name"));
        Assert.assertEquals(sampleData.p1.getUuid().toString(), json.getJsonObject(0).getString("uuid"));
    }

    @Test
    public void getAffectedProjectInvalidTest() throws Exception {
        new SampleData();
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/blah/projects").request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void getAllVulnerabilitiesTest() throws Exception {
        new SampleData();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertEquals(String.valueOf(5), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray json = parseJsonArray(response);
        Assert.assertNotNull(json);
        Assert.assertEquals(5, json.size());
        Assert.assertEquals("INT-1", json.getJsonObject(0).getString("vulnId"));
        Assert.assertEquals("INT-2", json.getJsonObject(1).getString("vulnId"));
        Assert.assertEquals("INT-3", json.getJsonObject(2).getString("vulnId"));
        Assert.assertEquals("INT-4", json.getJsonObject(3).getString("vulnId"));
        Assert.assertEquals("INT-5", json.getJsonObject(4).getString("vulnId"));
    }

    @Test
    public void createVulnerabilityTest() throws Exception {
        new CweImporter().processCweDefinitions();
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("description", "Something is vulnerable")
                .add("cwes", Json.createArrayBuilder().add(Json.createObjectBuilder().add("cweId", 80)))
                .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)")
                .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L")
                .add("owaspRRVector", "SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3")
                .add("affectedComponents", Json.createArrayBuilder()
                        .add(Json.createObjectBuilder()
                                .add("identityType", "PURL")
                                .add("identity", "pkg:maven/com.acme/acme-app")
                                .add("versionType", "RANGE")
                                .add("versionEndIncluding", "1.2.3")))
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assert.assertEquals(201, response.getStatus(), 0);
        JsonObject json = parseJsonObject(response);
        Assert.assertNotNull(json);
        Assert.assertEquals("ACME-1", json.getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getString("source"));
        Assert.assertEquals("Something is vulnerable", json.getString("description"));
        Assert.assertEquals(6.0, json.getJsonNumber("cvssV2BaseScore").doubleValue(), 0);
        Assert.assertEquals(6.4, json.getJsonNumber("cvssV2ImpactSubScore").doubleValue(), 0);
        Assert.assertEquals(6.8, json.getJsonNumber("cvssV2ExploitabilitySubScore").doubleValue(), 0);
        Assert.assertEquals("(AV:N/AC:M/Au:S/C:P/I:P/A:P)", json.getString("cvssV2Vector"));
        Assert.assertEquals(6.3, json.getJsonNumber("cvssV3BaseScore").doubleValue(), 0);
        Assert.assertEquals(3.4, json.getJsonNumber("cvssV3ImpactSubScore").doubleValue(), 0);
        Assert.assertEquals(2.8, json.getJsonNumber("cvssV3ExploitabilitySubScore").doubleValue(), 0);
        Assert.assertEquals("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", json.getString("cvssV3Vector"));
        Assert.assertEquals(1.0, json.getJsonNumber("owaspRRLikelihoodScore").doubleValue(), 0);
        Assert.assertEquals(1.3, json.getJsonNumber("owaspRRTechnicalImpactScore").doubleValue(), 0);
        Assert.assertEquals(1.8, json.getJsonNumber("owaspRRBusinessImpactScore").doubleValue(), 0);
        Assert.assertEquals("SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3", json.getString("owaspRRVector"));
        Assert.assertEquals("MEDIUM", json.getString("severity"));
        Assert.assertNotNull(json.getJsonObject("cwe"));
        Assert.assertEquals(80, json.getJsonObject("cwe").getInt("cweId"));
        Assert.assertEquals(1, json.getJsonArray("cwes").size());
        Assert.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId"));
        Assert.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getString("uuid")));

        // Verify that VulnerableSoftware records and attributions thereof have been created correctly
        final Vulnerability vuln = qm.getVulnerabilityByVulnId(Vulnerability.Source.INTERNAL, "ACME-1");
        Assert.assertNotNull(vuln);
        Assert.assertEquals(1, vuln.getVulnerableSoftware().size());
        final List<AffectedVersionAttribution> attributions = qm.getAffectedVersionAttributions(vuln, vuln.getVulnerableSoftware().get(0));
        Assert.assertEquals(1, attributions.size());
        Assert.assertEquals(Vulnerability.Source.INTERNAL, attributions.get(0).getSource());
    }

    @Test
    public void createVulnerabilityWithBadOwaspVectorTest() throws Exception {
        new CweImporter().processCweDefinitions();
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("description", "Something is vulnerable")
                .add("cwes", Json.createArrayBuilder().add(Json.createObjectBuilder().add("cweId", 80)))
                .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)")
                .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L")
                .add("owaspRRVector", "SL:1/M:1/O:a/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3")
                .add("affectedComponents", Json.createArrayBuilder()
                        .add(Json.createObjectBuilder()
                                .add("identityType", "PURL")
                                .add("identity", "pkg:maven/com.acme/acme-app")
                                .add("versionType", "RANGE")
                                .add("versionEndIncluding", "1.2.3")))
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assert.assertEquals(400, response.getStatus(), 0);
        String body = getPlainTextBody(response);
        Assert.assertNotNull(body);
        Assert.assertEquals("Provided vector SL:1/M:1/O:a/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3 does not match OWASP RR Vector pattern SL:\\d/M:\\d/O:\\d/S:\\d/ED:\\d/EE:\\d/A:\\d/ID:\\d/LC:\\d/LI:\\d/LAV:\\d/LAC:\\d/FD:\\d/RD:\\d/NC:\\d/PV:\\d", body);
    }

    /**
     * Ensure that pre-v4.5.0 behavior of setting CWE via a single object
     * still works, and both "cwe" and "cwes" fields are returned in the response.
     */
    @Test
    public void createVulnerabilityCwePreV450CompatTest() throws Exception {
        new CweImporter().processCweDefinitions();
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("cwe", Json.createObjectBuilder().add("cweId", 80))
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assert.assertEquals(201, response.getStatus(), 0);
        JsonObject json = parseJsonObject(response);
        Assert.assertNotNull(json);
        Assert.assertEquals("ACME-1", json.getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getString("source"));
        Assert.assertNotNull(json.getJsonObject("cwe"));
        Assert.assertEquals(80, json.getJsonObject("cwe").getInt("cweId"));
        Assert.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonObject("cwe").getString("name"));
        Assert.assertEquals(1, json.getJsonArray("cwes").size());
        Assert.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId"));
        Assert.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getString("uuid")));
    }

    @Test
    public void createVulnerabilityDuplicateTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("description", "Something is vulnerable")
                .add("cwes", Json.createArrayBuilder().add(Json.createObjectBuilder().add("cweId", 80)))
                .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)")
                .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L")
                .add("owaspRRVector", "SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3")
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assert.assertEquals(409, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("A vulnerability with the specified vulnId already exists.", body);
    }

    @Test
    public void updateVulnerabilityTest() throws Exception {
        new CweImporter().processCweDefinitions();
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln = qm.createVulnerability(vuln, false);
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("source", "INTERNAL")
                .add("description", "Something is vulnerable")
                .add("cwes", Json.createArrayBuilder().add(Json.createObjectBuilder().add("cweId", 80)))
                .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)")
                .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L")
                .add("owaspRRVector", "SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3")
                .add("uuid", vuln.getUuid().toString())
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(payload.toString()));
        Assert.assertEquals(200, response.getStatus(), 0);
        JsonObject json = parseJsonObject(response);
        Assert.assertNotNull(json);
        Assert.assertEquals("ACME-1", json.getString("vulnId"));
        Assert.assertEquals("INTERNAL", json.getString("source"));
        Assert.assertEquals("Something is vulnerable", json.getString("description"));
        Assert.assertEquals(6.0, json.getJsonNumber("cvssV2BaseScore").doubleValue(), 0);
        Assert.assertEquals(6.4, json.getJsonNumber("cvssV2ImpactSubScore").doubleValue(), 0);
        Assert.assertEquals(6.8, json.getJsonNumber("cvssV2ExploitabilitySubScore").doubleValue(), 0);
        Assert.assertEquals("(AV:N/AC:M/Au:S/C:P/I:P/A:P)", json.getString("cvssV2Vector"));
        Assert.assertEquals(6.3, json.getJsonNumber("cvssV3BaseScore").doubleValue(), 0);
        Assert.assertEquals(3.4, json.getJsonNumber("cvssV3ImpactSubScore").doubleValue(), 0);
        Assert.assertEquals(2.8, json.getJsonNumber("cvssV3ExploitabilitySubScore").doubleValue(), 0);
        Assert.assertEquals(1.0, json.getJsonNumber("owaspRRLikelihoodScore").doubleValue(), 0);
        Assert.assertEquals(1.3, json.getJsonNumber("owaspRRTechnicalImpactScore").doubleValue(), 0);
        Assert.assertEquals(1.8, json.getJsonNumber("owaspRRBusinessImpactScore").doubleValue(), 0);
        Assert.assertEquals("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", json.getString("cvssV3Vector"));
        Assert.assertEquals("SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3", json.getString("owaspRRVector"));
        Assert.assertEquals("MEDIUM", json.getString("severity"));
        Assert.assertEquals(1, json.getJsonArray("cwes").size());
        Assert.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId"));
        Assert.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name"));
        Assert.assertTrue(UuidUtil.isValidUUID(json.getString("uuid")));
    }

    @Test
    public void updateVulnerabilityInvalidTest() {
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("description", "Something is vulnerable")
                .add("cwes", Json.createArrayBuilder().add(Json.createObjectBuilder().add("cweId", 80)))
                .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)")
                .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L")
                .add("owaspRRVector", "SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3")
                .add("uuid", UUID.randomUUID().toString())
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(payload.toString()));
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void updateVulnerabilityUnchangableTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-2")
                .add("description", "Something is vulnerable")
                .add("cwes", Json.createArrayBuilder().add(Json.createObjectBuilder().add("cweId", 80)))
                .add("cvssV2Vector", "(AV:N/AC:M/Au:S/C:P/I:P/A:P)")
                .add("cvssV3Vector", "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L")
                .add("owaspRRVector", "SL:1/M:1/O:0/S:2/ED:1/EE:1/A:1/ID:1/LC:2/LI:1/LAV:1/LAC:1/FD:1/RD:1/NC:2/PV:3")
                .add("uuid", vuln.getUuid().toString())
                .build();
        Response response = target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(payload.toString()));
        Assert.assertEquals(406, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnId may not be changed.", body);
    }

    @Test
    public void deleteVulnerabilityTest() {
        final VulnerableSoftware vs = qm.persist(new VulnerableSoftware());
        var vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln.setVulnerableSoftware(List.of(vs));
        vuln = qm.createVulnerability(vuln, false);
        final AffectedVersionAttribution attribution = qm.persist(new AffectedVersionAttribution(Vulnerability.Source.INTERNAL, vuln, vs));

        final Response response = target(V1_VULNERABILITY + "/" + vuln.getUuid()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(204, response.getStatus());
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));

        Assert.assertNull(qm.getObjectByUuid(AffectedVersionAttribution.class, attribution.getUuid()));
        Assert.assertNotNull(qm.getObjectByUuid(VulnerableSoftware.class, vs.getUuid()));
        Assert.assertNull(qm.getObjectByUuid(Vulnerability.class, vuln.getUuid()));
    }

    @Test
    public void assignVulnerabilityTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/ACME-1/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    public void assignVulnerabilityInvalidVulnerabilityTest() {
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/BLAH/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void assignVulnerabilityInvalidComponentTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/ACME-1/component/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The component could not be found.", body);
    }

    @Test
    public void assignVulnerabilityByUuidTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln = qm.createVulnerability(vuln, false);
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    public void assignVulnerabilityByUuidInvalidVulnerabilityTest() {
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/" + UUID.randomUUID().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void assignVulnerabilityByUuidInvalidComponentTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln = qm.createVulnerability(vuln, false);
        Response response = target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The component could not be found.", body);
    }

    @Test
    public void unassignVulnerabilityTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/ACME-1/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    public void unassignVulnerabilityInvalidVulnerabilityTest() {
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/BLAH/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void unassignVulnerabilityInvalidComponentTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        Response response = target(V1_VULNERABILITY + "/source/INTERNAL/vuln/ACME-1/component/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The component could not be found.", body);
    }

    @Test
    public void unassignVulnerabilityByUuidTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln = qm.createVulnerability(vuln, false);
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(200, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    public void unassignVulnerabilityByUuidInvalidVulnerabilityTest() {
        Project project = qm.createProject("Acme Example", null, null, null, null, null, false, false);
        Component comp = new Component();
        comp.setProject(project);
        comp.setName("Test Component");
        comp = qm.createComponent(comp, false);
        Response response = target(V1_VULNERABILITY + "/" + UUID.randomUUID().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    public void unassignVulnerabilityByUuidInvalidComponentTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln = qm.createVulnerability(vuln, false);
        Response response = target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assert.assertEquals(404, response.getStatus(), 0);
        Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assert.assertEquals("The component could not be found.", body);
    }

    private class SampleData {
        final Project p1;
        final Project p2;
        final Component c1;
        final Component c2;
        final Component c3;
        final Vulnerability v1;
        final Vulnerability v2;
        final Vulnerability v3;
        final Vulnerability v4;
        final Vulnerability v5;
        final VulnerableSoftware vs1;

        SampleData() throws Exception {
            p1 = qm.createProject("Project 1", null, null, null, null, null, true, false);
            p2 = qm.createProject("Project 2", null, null, null, null, null, true, false);

            new CweImporter().processCweDefinitions();
            c1 = new Component();
            c1.setProject(p1);
            c1.setName("Component 1");
            c1.setMd5("2AAF520D1F19AECF246A0995D91E93A5");
            c1.setSha1("3D5A54669CF8D4ED55B7DF5751FA18C3F72F0CFB");
            c1.setSha256("47602D7DFE910AD941FEA52E85E6E3F1C175434B0E6E261C31C766FE4C078A25");

            c2 = new Component();
            c2.setProject(p1);
            c2.setName("Component 2");
            c1.setMd5("5EABD62FA03D159A96C77E6EEB6C7027");
            c1.setSha1("108BCF94A1C0E0F915B935C97F6BB9E50FB7C246");
            c1.setSha256("418716B003FE0268B6521EF7ACBED13F5BA491D593896D5DEB2058C42D87002D");

            // Identical component to C2, but in a different project
            c3 = new Component();
            c3.setProject(p2);
            c3.setName("Component 2");
            c3.setMd5("5EABD62FA03D159A96C77E6EEB6C7027");
            c3.setSha1("108BCF94A1C0E0F915B935C97F6BB9E50FB7C246");
            c3.setSha256("418716B003FE0268B6521EF7ACBED13F5BA491D593896D5DEB2058C42D87002D");

            qm.createComponent(c1, false);
            qm.createComponent(c2, false);
            qm.createComponent(c3, false);

            v1 = new Vulnerability();
            v1.setVulnId("INT-1");
            v1.setSource(Vulnerability.Source.INTERNAL);
            v1.setSeverity(Severity.CRITICAL);
            v1.setDescription("Description 1");

            vs1 = new VulnerableSoftware();
            qm.persist(vs1);
            qm.persist(new AffectedVersionAttribution(Vulnerability.Source.INTERNAL, v1, vs1));
            v1.setVulnerableSoftware(List.of(vs1));

            v2 = new Vulnerability();
            v2.setVulnId("INT-2");
            v2.setSource(Vulnerability.Source.INTERNAL);
            v2.setSeverity(Severity.HIGH);
            v2.setDescription("Description 2");

            v3 = new Vulnerability();
            v3.setVulnId("INT-3");
            v3.setSource(Vulnerability.Source.INTERNAL);
            v3.setSeverity(Severity.MEDIUM);
            v3.setDescription("Description 3");

            v4 = new Vulnerability();
            v4.setVulnId("INT-4");
            v4.setSource(Vulnerability.Source.INTERNAL);
            v4.setSeverity(Severity.LOW);
            v4.setDescription("Description 4");

            v5 = new Vulnerability();
            v5.setVulnId("INT-5");
            v5.setSource(Vulnerability.Source.INTERNAL);
            v5.setSeverity(Severity.CRITICAL);
            v5.setDescription("Description 5");

            qm.createVulnerability(v1, false);
            qm.createVulnerability(v2, false);
            qm.createVulnerability(v3, false);
            qm.createVulnerability(v4, false);
            qm.createVulnerability(v5, false);
            qm.addVulnerability(v1, c1, AnalyzerIdentity.NONE);
            qm.addVulnerability(v2, c1, AnalyzerIdentity.NONE);
            qm.addVulnerability(v3, c1, AnalyzerIdentity.NONE);
            qm.addVulnerability(v4, c2, AnalyzerIdentity.NONE);
            qm.addVulnerability(v5, c2, AnalyzerIdentity.NONE);
            qm.addVulnerability(v4, c3, AnalyzerIdentity.NONE);
            qm.addVulnerability(v5, c3, AnalyzerIdentity.NONE);

            qm.makeAnalysis(c1, v3, AnalysisState.FALSE_POSITIVE, AnalysisJustification.CODE_NOT_REACHABLE, AnalysisResponse.WILL_NOT_FIX, "Analysis details here", true);
            qm.makeAnalysis(c3, v5, AnalysisState.NOT_AFFECTED, AnalysisJustification.CODE_NOT_REACHABLE, AnalysisResponse.WILL_NOT_FIX, "Analysis details here", true);
        }
    }
}
