/*
 * 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) OWASP Foundation. All Rights Reserved.
 */
package org.dependencytrack.resources.v1;

import alpine.common.util.UuidUtil;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import net.javacrumbs.jsonunit.core.Option;
import org.dependencytrack.JerseyTestExtension;
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.ConfigPropertyConstants;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Severity;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.model.VulnerableSoftware;
import org.dependencytrack.tasks.scanners.AnalyzerIdentity;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import java.util.List;
import java.util.UUID;

import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;

class VulnerabilityResourceTest extends ResourceTest {

    @RegisterExtension
    public static JerseyTestExtension jersey = new JerseyTestExtension(
            () -> new ResourceConfig(VulnerabilityResource.class)
                    .register(ApiFilter.class)
                    .register(AuthenticationFilter.class));

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

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

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

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

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

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

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

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

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

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

    @Test
    void getVulnerabilityByVulnIdACLEnabledTest() {
        SampleData sampleData = new SampleData();
        qm.createConfigProperty(
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
                "true",
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription());
        Response response = jersey.target(V1_VULNERABILITY + "/source/" + sampleData.v6.getSource() + "/vuln/" + sampleData.v6.getVulnId()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonObject json = parseJsonObject(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals("INT-6", json.getString("vulnId"));
        JsonArray components = json.getJsonArray("components");
        Assertions.assertNotNull(components);
        Assertions.assertEquals(1, components.size());
    }

    @Test
    void getVulnerabilityByVulnIdACLDisabledTest() {
        SampleData sampleData = new SampleData();
        qm.createConfigProperty(
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
                "false",
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription());
        Response response = jersey.target(V1_VULNERABILITY + "/source/" + sampleData.v6.getSource() + "/vuln/" + sampleData.v6.getVulnId()).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonObject json = parseJsonObject(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals("INT-6", json.getString("vulnId"));
        JsonArray components = json.getJsonArray("components");
        Assertions.assertNotNull(components);
        Assertions.assertEquals(2, components.size());
    }

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

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

    @Test
    void getAffectedProjectACLEnabledTest() {
        SampleData sampleData = new SampleData();
        qm.createConfigProperty(
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
                "true",
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription());
        Response response = jersey.target(V1_VULNERABILITY + "/source/" + sampleData.v6.getSource() + "/vuln/" + sampleData.v6.getVulnId() + "/projects").request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assertions.assertEquals(200, response.getStatus(), 0);
        JsonArray json = parseJsonArray(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals(1, json.size());
        Assertions.assertEquals("Project 1", json.getJsonObject(0).getString("name"));
        Assertions.assertEquals(sampleData.p1.getUuid().toString(), json.getJsonObject(0).getString("uuid"));
    }

    @Test
    void getAffectedProjectACLDisabledTest() {
        SampleData sampleData = new SampleData();
        qm.createConfigProperty(
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
                "false",
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription());
        Response response = jersey.target(V1_VULNERABILITY + "/source/" + sampleData.v6.getSource() + "/vuln/" + sampleData.v6.getVulnId() + "/projects").request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assertions.assertEquals(200, response.getStatus(), 0);
        JsonArray json = parseJsonArray(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals(2, json.size());
        assertThat(json).satisfiesExactlyInAnyOrder(
                jsonValue -> {
                    final JsonObject projectObject = jsonValue.asJsonObject();
                    assertThat(projectObject.getString("name")).isEqualTo("Project 1");
                    assertThat(projectObject.getString("uuid")).isEqualTo(sampleData.p1.getUuid().toString());
                },
                jsonValue -> {
                    final JsonObject projectObject = jsonValue.asJsonObject();
                    assertThat(projectObject.getString("name")).isEqualTo("Project 2");
                    assertThat(projectObject.getString("uuid")).isEqualTo(sampleData.p2.getUuid().toString());
                });
    }

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

    @Test
    void getAllVulnerabilitiesACLEnabledTest() {
        new SampleData();
        qm.createConfigProperty(
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
                "true",
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription());
        Response response = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertEquals(String.valueOf(6), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray jsons = parseJsonArray(response);
        Assertions.assertNotNull(jsons);
        Assertions.assertEquals(6, jsons.size());
        for (JsonValue json : jsons) {
            JsonArray components = json.asJsonObject().getJsonArray("components");
            Assertions.assertNotNull(components);
            Assertions.assertEquals(1, components.size());
        }
    }

    @Test
    void getAllVulnerabilitiesACLDisabledTest() {
        new SampleData();
        qm.createConfigProperty(
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
                "false",
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
                ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription());
        Response response = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .get(Response.class);
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertEquals(String.valueOf(6), response.getHeaderString(TOTAL_COUNT_HEADER));
        JsonArray jsons = parseJsonArray(response);
        Assertions.assertNotNull(jsons);
        Assertions.assertEquals(6, jsons.size());
        for (int i = 0; i < 3; i++) {
            JsonArray components = jsons.get(i).asJsonObject().getJsonArray("components");
            Assertions.assertNotNull(components);
            Assertions.assertEquals(1, components.size());
        }
        JsonArray components = jsons.get(3).asJsonObject().getJsonArray("components");
        Assertions.assertNotNull(components);
        Assertions.assertEquals(2, components.size());
        components = jsons.get(4).asJsonObject().getJsonArray("components");
        Assertions.assertNotNull(components);
        Assertions.assertEquals(2, components.size());
        components = jsons.get(5).asJsonObject().getJsonArray("components");
        Assertions.assertNotNull(components);
        Assertions.assertEquals(2, components.size());
    }


    @Test
    void createVulnerabilityTest() {
        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 = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assertions.assertEquals(201, response.getStatus(), 0);
        JsonObject json = parseJsonObject(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals("ACME-1", json.getString("vulnId"));
        Assertions.assertEquals("INTERNAL", json.getString("source"));
        Assertions.assertEquals("Something is vulnerable", json.getString("description"));
        Assertions.assertEquals(6.0, json.getJsonNumber("cvssV2BaseScore").doubleValue(), 0);
        Assertions.assertEquals(6.4, json.getJsonNumber("cvssV2ImpactSubScore").doubleValue(), 0);
        Assertions.assertEquals(6.8, json.getJsonNumber("cvssV2ExploitabilitySubScore").doubleValue(), 0);
        Assertions.assertEquals("AV:N/AC:M/Au:S/C:P/I:P/A:P", json.getString("cvssV2Vector"));
        Assertions.assertEquals(6.3, json.getJsonNumber("cvssV3BaseScore").doubleValue(), 0);
        Assertions.assertEquals(3.4, json.getJsonNumber("cvssV3ImpactSubScore").doubleValue(), 0);
        Assertions.assertEquals(2.8, json.getJsonNumber("cvssV3ExploitabilitySubScore").doubleValue(), 0);
        Assertions.assertEquals("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", json.getString("cvssV3Vector"));
        Assertions.assertEquals(1.0, json.getJsonNumber("owaspRRLikelihoodScore").doubleValue(), 0);
        Assertions.assertEquals(1.25, json.getJsonNumber("owaspRRTechnicalImpactScore").doubleValue(), 0);
        Assertions.assertEquals(1.75, json.getJsonNumber("owaspRRBusinessImpactScore").doubleValue(), 0);
        Assertions.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"));
        Assertions.assertEquals("MEDIUM", json.getString("severity"));
        Assertions.assertNotNull(json.getJsonObject("cwe"));
        Assertions.assertEquals(80, json.getJsonObject("cwe").getInt("cweId"));
        Assertions.assertEquals(1, json.getJsonArray("cwes").size());
        Assertions.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId"));
        Assertions.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name"));
        Assertions.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");
        Assertions.assertNotNull(vuln);
        Assertions.assertEquals(1, vuln.getVulnerableSoftware().size());
        final List<AffectedVersionAttribution> attributions = qm.getAffectedVersionAttributions(vuln, vuln.getVulnerableSoftware().get(0));
        Assertions.assertEquals(1, attributions.size());
        Assertions.assertEquals(Vulnerability.Source.INTERNAL, attributions.get(0).getSource());
    }

    @Test
    void createVulnerabilityWithBadOwaspVectorTest() {
        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 = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assertions.assertEquals(400, response.getStatus(), 0);
        String body = getPlainTextBody(response);
        Assertions.assertNotNull(body);
        Assertions.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
    void createVulnerabilityCwePreV450CompatTest() {
        JsonObject payload = Json.createObjectBuilder()
                .add("vulnId", "ACME-1")
                .add("cwe", Json.createObjectBuilder().add("cweId", 80))
                .build();
        Response response = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assertions.assertEquals(201, response.getStatus(), 0);
        JsonObject json = parseJsonObject(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals("ACME-1", json.getString("vulnId"));
        Assertions.assertEquals("INTERNAL", json.getString("source"));
        Assertions.assertNotNull(json.getJsonObject("cwe"));
        Assertions.assertEquals(80, json.getJsonObject("cwe").getInt("cweId"));
        Assertions.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonObject("cwe").getString("name"));
        Assertions.assertEquals(1, json.getJsonArray("cwes").size());
        Assertions.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId"));
        Assertions.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name"));
        Assertions.assertTrue(UuidUtil.isValidUUID(json.getString("uuid")));
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(payload.toString()));
        Assertions.assertEquals(409, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("A vulnerability with the specified vulnId already exists.", body);
    }

    @Test
    void updateVulnerabilityTest() {
        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 = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(payload.toString()));
        Assertions.assertEquals(200, response.getStatus(), 0);
        JsonObject json = parseJsonObject(response);
        Assertions.assertNotNull(json);
        Assertions.assertEquals("ACME-1", json.getString("vulnId"));
        Assertions.assertEquals("INTERNAL", json.getString("source"));
        Assertions.assertEquals("Something is vulnerable", json.getString("description"));
        Assertions.assertEquals(6.0, json.getJsonNumber("cvssV2BaseScore").doubleValue(), 0);
        Assertions.assertEquals(6.4, json.getJsonNumber("cvssV2ImpactSubScore").doubleValue(), 0);
        Assertions.assertEquals(6.8, json.getJsonNumber("cvssV2ExploitabilitySubScore").doubleValue(), 0);
        Assertions.assertEquals("AV:N/AC:M/Au:S/C:P/I:P/A:P", json.getString("cvssV2Vector"));
        Assertions.assertEquals(6.3, json.getJsonNumber("cvssV3BaseScore").doubleValue(), 0);
        Assertions.assertEquals(3.4, json.getJsonNumber("cvssV3ImpactSubScore").doubleValue(), 0);
        Assertions.assertEquals(2.8, json.getJsonNumber("cvssV3ExploitabilitySubScore").doubleValue(), 0);
        Assertions.assertEquals(1.0, json.getJsonNumber("owaspRRLikelihoodScore").doubleValue(), 0);
        Assertions.assertEquals(1.25, json.getJsonNumber("owaspRRTechnicalImpactScore").doubleValue(), 0);
        Assertions.assertEquals(1.75, json.getJsonNumber("owaspRRBusinessImpactScore").doubleValue(), 0);
        Assertions.assertEquals("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", json.getString("cvssV3Vector"));
        Assertions.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"));
        Assertions.assertEquals("MEDIUM", json.getString("severity"));
        Assertions.assertEquals(1, json.getJsonArray("cwes").size());
        Assertions.assertEquals(80, json.getJsonArray("cwes").getJsonObject(0).getInt("cweId"));
        Assertions.assertEquals("Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)", json.getJsonArray("cwes").getJsonObject(0).getString("name"));
        Assertions.assertTrue(UuidUtil.isValidUUID(json.getString("uuid")));
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(payload.toString()));
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(payload.toString()));
        Assertions.assertEquals(406, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The vulnId may not be changed.", body);
    }

    @Test
    void testReconcileVulnerableSoftwareWithInternalSource() {
        Response response = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .put(Entity.json(/* language=JSON */ """
                        {
                          "vulnId": "INT-001",
                          "source": "INTERNAL",
                          "affectedComponents": [
                            {
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@1.2.3",
                              "versionType": "EXACT"
                            },
                            {
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@3.2.1",
                              "versionType": "EXACT"
                            }
                          ]
                        }
                        """));
        assertThat(response.getStatus()).isEqualTo(201);
        final JsonObject createdVulnJsonObject = parseJsonObject(response);
        assertThatJson(createdVulnJsonObject.toString())
                .withOptions(Option.IGNORING_ARRAY_ORDER)
                .isEqualTo(/* language=JSON */ """
                        {
                          "uuid": "${json-unit.any-string}",
                          "vulnId": "INT-001",
                          "source": "INTERNAL",
                          "components": [],
                          "serviceComponents": [],
                          "severity": "UNASSIGNED",
                          "affectedComponents": [
                            {
                              "uuid": "${json-unit.any-string}",
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@1.2.3",
                              "versionType": "EXACT",
                              "version": "1.2.3",
                              "affectedVersionAttributions": [
                                {
                                  "uuid": "${json-unit.any-string}",
                                  "source": "INTERNAL",
                                  "firstSeen": "${json-unit.any-number}",
                                  "lastSeen": "${json-unit.any-number}"
                                }
                              ]
                            },
                            {
                              "uuid": "${json-unit.any-string}",
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@3.2.1",
                              "versionType": "EXACT",
                              "version": "3.2.1",
                              "affectedVersionAttributions": [
                                {
                                  "uuid": "${json-unit.any-string}",
                                  "source": "INTERNAL",
                                  "firstSeen": "${json-unit.any-number}",
                                  "lastSeen": "${json-unit.any-number}"
                                }
                              ]
                            }
                          ],
                          "affectedActiveProjectCount": 0,
                          "affectedInactiveProjectCount": 0,
                          "affectedProjectCount": 0
                        }
                        """);

        final String vulnUuid = createdVulnJsonObject.getString("uuid");
        response = jersey.target(V1_VULNERABILITY).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.json(/* language=JSON */ """
                        {
                          "uuid": "%s",
                          "vulnId": "INT-001",
                          "source": "INTERNAL",
                          "affectedComponents": [
                            {
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@1.2.3",
                              "versionType": "EXACT"
                            },
                            {
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib",
                              "versionType": "RANGE",
                              "versionStartIncluding": "3.2.1"
                            }
                          ]
                        }
                        """.formatted(vulnUuid)));
        assertThat(response.getStatus()).isEqualTo(200);
        assertThatJson(getPlainTextBody(response))
                .withOptions(Option.IGNORING_ARRAY_ORDER)
                .isEqualTo(/* language=JSON */ """
                        {
                          "uuid": "${json-unit.any-string}",
                          "vulnId": "INT-001",
                          "source": "INTERNAL",
                          "aliases": [],
                          "components": [],
                          "serviceComponents": [],
                          "severity": "UNASSIGNED",
                          "affectedComponents": [
                            {
                              "uuid": "${json-unit.any-string}",
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@1.2.3",
                              "versionType": "EXACT",
                              "version": "1.2.3",
                              "affectedVersionAttributions": [
                                {
                                  "uuid": "${json-unit.any-string}",
                                  "source": "INTERNAL",
                                  "firstSeen": "${json-unit.any-number}",
                                  "lastSeen": "${json-unit.any-number}"
                                }
                              ]
                            },
                            {
                              "uuid": "${json-unit.any-string}",
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib",
                              "versionType": "RANGE",
                              "versionStartIncluding": "3.2.1",
                              "affectedVersionAttributions": [
                                {
                                  "uuid": "${json-unit.any-string}",
                                  "source": "INTERNAL",
                                  "firstSeen": "${json-unit.any-number}",
                                  "lastSeen": "${json-unit.any-number}"
                                }
                              ]
                            }
                          ],
                          "affectedActiveProjectCount": 0,
                          "affectedInactiveProjectCount": 0,
                          "affectedProjectCount": 0
                        }
                        """);

        response = jersey.target(V1_VULNERABILITY + "/source/INTERNAL/vuln/INT-001").request()
                .header(X_API_KEY, apiKey)
                .get();
        assertThat(response.getStatus()).isEqualTo(200);
        assertThatJson(getPlainTextBody(response))
                .withOptions(Option.IGNORING_ARRAY_ORDER)
                .isEqualTo(/* language=JSON */ """
                        {
                          "uuid": "${json-unit.any-string}",
                          "vulnId": "INT-001",
                          "source": "INTERNAL",
                          "aliases": [],
                          "components": [],
                          "severity": "UNASSIGNED",
                          "affectedComponents": [
                            {
                              "uuid": "${json-unit.any-string}",
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib@1.2.3",
                              "versionType": "EXACT",
                              "version": "1.2.3",
                              "affectedVersionAttributions": [
                                {
                                  "uuid": "${json-unit.any-string}",
                                  "source": "INTERNAL",
                                  "firstSeen": "${json-unit.any-number}",
                                  "lastSeen": "${json-unit.any-number}"
                                }
                              ]
                            },
                            {
                              "uuid": "${json-unit.any-string}",
                              "identityType": "PURL",
                              "identity": "pkg:maven/com.acme/acme-lib",
                              "versionType": "RANGE",
                              "versionStartIncluding": "3.2.1",
                              "affectedVersionAttributions": [
                                {
                                  "uuid": "${json-unit.any-string}",
                                  "source": "INTERNAL",
                                  "firstSeen": "${json-unit.any-number}",
                                  "lastSeen": "${json-unit.any-number}"
                                }
                              ]
                            }
                          ],
                          "affectedActiveProjectCount": 0,
                          "affectedInactiveProjectCount": 0,
                          "affectedProjectCount": 0
                        }
                        """);
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/" + vuln.getUuid()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assertions.assertEquals(204, response.getStatus());
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));

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

    @Test
    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 = jersey.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));
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/source/INTERNAL/vuln/BLAH/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    void assignVulnerabilityInvalidComponentTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        qm.createVulnerability(vuln, false);
        Response response = jersey.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));
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The component could not be found.", body);
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/" + UUID.randomUUID().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .post(Entity.entity(null, MediaType.APPLICATION_JSON));
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The vulnerability could not be found.", body);
    }

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

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/source/INTERNAL/vuln/ACME-1/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/source/INTERNAL/vuln/BLAH/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The vulnerability could not be found.", body);
    }

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

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assertions.assertEquals(200, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
    }

    @Test
    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 = jersey.target(V1_VULNERABILITY + "/" + UUID.randomUUID().toString() + "/component/" + comp.getUuid().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.assertEquals("The vulnerability could not be found.", body);
    }

    @Test
    void unassignVulnerabilityByUuidInvalidComponentTest() {
        Vulnerability vuln = new Vulnerability();
        vuln.setVulnId("ACME-1");
        vuln.setSource(Vulnerability.Source.INTERNAL);
        vuln = qm.createVulnerability(vuln, false);
        Response response = jersey.target(V1_VULNERABILITY + "/" + vuln.getUuid().toString() + "/component/" + UUID.randomUUID().toString()).request()
                .header(X_API_KEY, apiKey)
                .delete();
        Assertions.assertEquals(404, response.getStatus(), 0);
        Assertions.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
        String body = getPlainTextBody(response);
        Assertions.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 Vulnerability v6;
        final VulnerableSoftware vs1;

        SampleData() {
            p1 = qm.createProject("Project 1", null, null, null, null, null, true, false);
            p1.addAccessTeam(team);

            p2 = qm.createProject("Project 2", null, null, null, null, null, true, false);

            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");

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

            qm.createVulnerability(v1, false);
            qm.createVulnerability(v2, false);
            qm.createVulnerability(v3, false);
            qm.createVulnerability(v4, false);
            qm.createVulnerability(v5, false);
            qm.createVulnerability(v6, 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.addVulnerability(v6, c1, AnalyzerIdentity.NONE);
            qm.addVulnerability(v6, 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);
        }
    }
}
