<commit_before>package org.openkilda.functionaltests.spec.flows

import static org.junit.Assume.assumeTrue
import static org.openkilda.testing.Constants.NON_EXISTENT_FLOW_ID
import static org.openkilda.testing.Constants.RULES_DELETION_TIME
import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME
import static org.openkilda.testing.Constants.WAIT_OFFSET

import org.openkilda.functionaltests.BaseSpecification
import org.openkilda.functionaltests.helpers.PathHelper
import org.openkilda.functionaltests.helpers.Wrappers
import org.openkilda.messaging.error.MessageError
import org.openkilda.messaging.info.event.IslChangeType
import org.openkilda.messaging.info.event.PathNode
import org.openkilda.messaging.payload.flow.FlowCreatePayload
import org.openkilda.messaging.payload.flow.FlowEndpointPayload
import org.openkilda.messaging.payload.flow.FlowState
import org.openkilda.northbound.dto.v2.flows.SwapFlowPayload
import org.openkilda.testing.service.traffexam.TraffExamService
import org.openkilda.testing.tools.FlowTrafficExamBuilder

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.client.HttpClientErrorException
import spock.lang.Unroll

import javax.inject.Provider

class SwapEndpointSpec extends BaseSpecification {

    @Autowired
    Provider<TraffExamService> traffExamProvider

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with the same source and destination switches"() {
        given: "Two flows with the same source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any rule discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(switchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        switchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow1 << [getFirstFlow(switchPair, switchPair)] * 3
        flow2 << [getSecondFlow(switchPair, switchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with the same source and destination switches"() {
        given: "Two flows with the same source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(switchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        switchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow1 << [getFirstFlow(switchPair, switchPair)] * 4
        flow2 << [getSecondFlow(switchPair, switchPair, flow1)] * 4
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with the same source and different destination \
switches"() {
        given: "Two flows with the same source and different destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "src")] * 3
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 3
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with the same source and different destination \
switches"() {
        given: "Two flows with the same source and different destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "src")] * 4
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 4
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with different source and the same destination \
switches"() {
        given: "Two flows with different source and the same destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "dst")] * 3
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 3
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with different source and the same destination \
switches"() {
        given: "Two flows with different source and the same destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "dst")] * 4
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 4
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with different source and destination switches"() {
        given: "Two flows with different source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow2SwitchPair << [getDifferentNotNeighboringSwitchPair(flow1SwitchPair)] * 3
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 3
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with different source and destination switches"() {
        given: "Two flows with different source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow2SwitchPair << [getDifferentNotNeighboringSwitchPair(flow1SwitchPair)] * 4
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4
        flow2 << [
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "source", "portNumber",
                                flow1.source.portNumber), "source", "vlanId", flow1.source.vlanId),
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "destination", "portNumber",
                                flow1.destination.portNumber), "destination", "vlanId", flow1.destination.vlanId),
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "destination", "portNumber",
                                flow1.source.portNumber), "destination", "vlanId", flow1.source.vlanId),
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "source", "portNumber",
                                flow1.destination.portNumber), "source", "vlanId", flow1.destination.vlanId)
        ]
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    def "Unable to swap endpoints for existing flow and non-existing flow"() {
        given: "An active flow"
        def switchPair = topologyHelper.getNeighboringSwitchPair()
        def flow1 = flowHelper.randomFlow(switchPair)
        def flow2 = flowHelper.randomFlow(switchPair)
        flowHelper.addFlow(flow1)
        flow2.id = NON_EXISTENT_FLOW_ID

        when: "Try to swap endpoints for existing flow and non-existing flow"
        northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1.source),
                        flowHelper.toFlowEndpointV2(flow2.destination)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2.source),
                        flowHelper.toFlowEndpointV2(flow1.destination)))

        then: "An error is received (404 code)"
        def exc = thrown(HttpClientErrorException)
        exc.rawStatusCode == 404
        exc.responseBodyAsString.to(MessageError).errorMessage == "Can not swap endpoints for flows: " +
                "Flow ${flow2.id} not found"

        and: "Delete the flow"
        flowHelper.deleteFlow(flow1.id)
    }

    @Unroll
    def "Unable to swap #endpointsPart for two flows (#description)"() {
        given: "Three active flows"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)
        flowHelper.addFlow(flow3)

        when: "Try to swap #endpointsPart for two flows"
        northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "An error is received (409 code)"
        def exc = thrown(HttpClientErrorException)
        exc.rawStatusCode == 409
        exc.responseBodyAsString.to(MessageError).errorMessage == "Can not swap endpoints for flows: " +
                "Requested $conflictingEndpoint endpoint for flow '${flow1.id}' conflicts with existing " +
                "$conflictingEndpoint endpoint for flow '${flow3.id}'."

        and: "Delete flows"
        [flow1, flow2, flow3].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["ports and vlans", "ports and vlans", "vlans", "vlans", "ports", "ports", "ports", "ports"]
        description << ["the same ports and vlans on src switch",
                        "the same ports and vlans on dst switch",
                        "the same vlans on the same port on src switch",
                        "the same vlans on the same port on dst switch",
                        "no vlan vs vlan on the same port on src switch",
                        "no vlan vs vlan on the same port on dst switch",
                        "no vlans, both flows are on the same port on src switch",
                        "no vlans, both flows are on the same port on dst switch"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 8
        flow2SwitchPair << [getDifferentNotNeighboringSwitchPair(flow1SwitchPair)] * 8
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 6 +
                [getFirstFlow(flow1SwitchPair, flow2SwitchPair, true)] * 2
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 6 +
                [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1, true)] * 2
        flow3 << [
                getConflictingFlow(flow1SwitchPair, flow1, "source", changePropertyValue(
                        changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber), "vlanId",
                        flow2.destination.vlanId)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination", changePropertyValue(
                        changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber), "vlanId",
                        flow2.source.vlanId)),

                getConflictingFlow(flow1SwitchPair, flow1, "source",
                        changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination",
                        changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId)),

                getConflictingFlow(flow1SwitchPair, flow1, "source", changePropertyValue(
                        changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber), "vlanId", null)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination", changePropertyValue(
                        changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber), "vlanId", null)),

                getConflictingFlow(flow1SwitchPair, flow1, "source",
                        changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination",
                        changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber))
        ]
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(changePropertyValue(flow1.source, "portNumber",
                        flow2.destination.portNumber), "vlanId", flow2.destination.vlanId), flow1.destination,
                 flow2.source, changePropertyValue(changePropertyValue(flow2.destination, "portNumber",
                        flow1.source.portNumber), "vlanId", flow1.source.vlanId)],
                [flow1.source, changePropertyValue(changePropertyValue(flow1.destination, "portNumber",
                        flow2.source.portNumber), "vlanId", flow2.source.vlanId),
                 changePropertyValue(changePropertyValue(flow2.source, "portNumber",
                         flow1.destination.portNumber), "vlanId", flow1.destination.vlanId), flow2.destination],

                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId), flow1.destination,
                 flow2.source, changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],
                [flow1.source, changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId), flow2.destination],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber), flow1.destination,
                 flow2.source, changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],
                [flow1.source, changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber), flow2.destination],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber), flow1.destination,
                 flow2.source, changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],
                [flow1.source, changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber), flow2.destination]
        ]
        conflictingEndpoint << ["source", "destination"] * 4
    }

    @Unroll
    def "Unable to swap endpoints for two flows (#description)"() {
        given: "Two active flows"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for two flows"
        northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "An error is received (400 code)"
        def exc = thrown(HttpClientErrorException)
        exc.rawStatusCode == 400
        exc.responseBodyAsString.to(MessageError).errorMessage == "Can not swap endpoints for flows: " +
                "New requested endpoint for '$flow2.id' conflicts with existing endpoint for '$flow1.id'"<commit_msg>fix swapEnpointSpec according to a new logic<commit_after>package org.openkilda.functionaltests.spec.flows

import static org.junit.Assume.assumeTrue
import static org.openkilda.testing.Constants.NON_EXISTENT_FLOW_ID
import static org.openkilda.testing.Constants.RULES_DELETION_TIME
import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME
import static org.openkilda.testing.Constants.WAIT_OFFSET

import org.openkilda.functionaltests.BaseSpecification
import org.openkilda.functionaltests.helpers.PathHelper
import org.openkilda.functionaltests.helpers.Wrappers
import org.openkilda.messaging.error.MessageError
import org.openkilda.messaging.info.event.IslChangeType
import org.openkilda.messaging.info.event.PathNode
import org.openkilda.messaging.payload.flow.FlowCreatePayload
import org.openkilda.messaging.payload.flow.FlowEndpointPayload
import org.openkilda.messaging.payload.flow.FlowState
import org.openkilda.northbound.dto.v2.flows.SwapFlowPayload
import org.openkilda.testing.service.traffexam.TraffExamService
import org.openkilda.testing.tools.FlowTrafficExamBuilder

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.client.HttpClientErrorException
import spock.lang.Ignore
import spock.lang.Unroll

import javax.inject.Provider

class SwapEndpointSpec extends BaseSpecification {

    @Autowired
    Provider<TraffExamService> traffExamProvider

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with the same source and destination switches"() {
        given: "Two flows with the same source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any rule discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(switchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        switchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow1 << [getFirstFlow(switchPair, switchPair)] * 3
        flow2 << [getSecondFlow(switchPair, switchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with the same source and destination switches"() {
        given: "Two flows with the same source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(switchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        switchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow1 << [getFirstFlow(switchPair, switchPair)] * 4
        flow2 << [getSecondFlow(switchPair, switchPair, flow1)] * 4
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with the same source and different destination \
switches"() {
        given: "Two flows with the same source and different destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "src")] * 3
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 3
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with the same source and different destination \
switches"() {
        given: "Two flows with the same source and different destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "src")] * 4
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 4
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with different source and the same destination \
switches"() {
        given: "Two flows with different source and the same destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "dst")] * 3
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 3
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with different source and the same destination \
switches"() {
        given: "Two flows with different source and the same destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow2SwitchPair << [getHalfDifferentNotNeighboringSwitchPair(flow1SwitchPair, "dst")] * 4
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 4
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    @Unroll
    def "Able to swap #endpointsPart (#description) for two flows with different source and destination switches"() {
        given: "Two flows with different source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap #endpointsPart for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "#endpointsPart.capitalize() are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["vlans", "ports", "switches"]
        description << ["src1 <-> dst2, dst1 <-> src2"] * 3
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 3
        flow2SwitchPair << [getDifferentNotNeighboringSwitchPair(flow1SwitchPair)] * 3
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 3
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 3
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId),
                 changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId),
                 changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber),
                 changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber),
                 changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],

                [changePropertyValue(flow1.source, "datapath", flow2.destination.datapath),
                 changePropertyValue(flow1.destination, "datapath", flow2.source.datapath),
                 changePropertyValue(flow2.source, "datapath", flow1.destination.datapath),
                 changePropertyValue(flow2.destination, "datapath", flow1.source.datapath)],
        ]
    }

    @Unroll
    def "Able to swap endpoints (#description) for two flows with different source and destination switches"() {
        given: "Two flows with different source and destination switches"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for flows"
        def response = northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "Endpoints are successfully swapped"
        verifyEndpoints(response, flow1Src, flow1Dst, flow2Src, flow2Dst)
        verifyEndpoints(flow1, flow2, flow1Src, flow1Dst, flow2Src, flow2Dst)

        and: "Flows validation doesn't show any discrepancies"
        validateFlows(flow1, flow2)

        and: "Switch validation doesn't show any missing/excess rules and meters"
        validateSwitches(flow1SwitchPair)
        validateSwitches(flow2SwitchPair)

        and: "Delete flows"
        [flow1, flow2].each { flowHelper.deleteFlow(it.id) }

        where:
        description << ["src1 <-> src2", "dst1 <-> dst2", "src1 <-> dst2", "dst1 <-> src2"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 4
        flow2SwitchPair << [getDifferentNotNeighboringSwitchPair(flow1SwitchPair)] * 4
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4
        flow2 << [
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "source", "portNumber",
                                flow1.source.portNumber), "source", "vlanId", flow1.source.vlanId),
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "destination", "portNumber",
                                flow1.destination.portNumber), "destination", "vlanId", flow1.destination.vlanId),
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "destination", "portNumber",
                                flow1.source.portNumber), "destination", "vlanId", flow1.source.vlanId),
                changePropertyValue(
                        changePropertyValue(getFlowHelper().randomFlow(flow2SwitchPair), "source", "portNumber",
                                flow1.destination.portNumber), "source", "vlanId", flow1.destination.vlanId)
        ]
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [flow2.source, flow1.destination, flow1.source, flow2.destination],
                [flow1.source, flow2.destination, flow2.source, flow1.destination],
                [flow2.destination, flow1.destination, flow2.source, flow1.source],
                [flow1.source, flow2.source, flow1.destination, flow2.destination]
        ]
    }

    def "Unable to swap endpoints for existing flow and non-existing flow"() {
        given: "An active flow"
        def switchPair = topologyHelper.getNeighboringSwitchPair()
        def flow1 = flowHelper.randomFlow(switchPair)
        def flow2 = flowHelper.randomFlow(switchPair)
        flowHelper.addFlow(flow1)
        flow2.id = NON_EXISTENT_FLOW_ID

        when: "Try to swap endpoints for existing flow and non-existing flow"
        northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1.source),
                        flowHelper.toFlowEndpointV2(flow2.destination)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2.source),
                        flowHelper.toFlowEndpointV2(flow1.destination)))

        then: "An error is received (404 code)"
        def exc = thrown(HttpClientErrorException)
        exc.rawStatusCode == 404
        exc.responseBodyAsString.to(MessageError).errorMessage == "Can not swap endpoints for flows: " +
                "Flow ${flow2.id} not found"

        and: "Delete the flow"
        flowHelper.deleteFlow(flow1.id)
    }

    @Unroll
    def "Unable to swap #endpointsPart for two flows (#description)"() {
        given: "Three active flows"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)
        flowHelper.addFlow(flow3)

        when: "Try to swap #endpointsPart for two flows"
        northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "An error is received (409 code)"
        def exc = thrown(HttpClientErrorException)
        exc.rawStatusCode == 409
        // TODO check error message
        and: "Delete flows"
        [flow1, flow2, flow3].each { flowHelper.deleteFlow(it.id) }

        where:
        endpointsPart << ["ports and vlans", "ports and vlans", "vlans", "vlans", "ports", "ports"]
        description << ["the same ports and vlans on src switch",
                        "the same ports and vlans on dst switch",
                        "the same vlans on the same port on src switch",
                        "the same vlans on the same port on dst switch",
                        "no vlans, both flows are on the same port on src switch",
                        "no vlans, both flows are on the same port on dst switch"]
        flow1SwitchPair << [getTopologyHelper().getNotNeighboringSwitchPair()] * 6
        flow2SwitchPair << [getDifferentNotNeighboringSwitchPair(flow1SwitchPair)] * 6
        flow1 << [getFirstFlow(flow1SwitchPair, flow2SwitchPair)] * 4 +
                [getFirstFlow(flow1SwitchPair, flow2SwitchPair, true)] * 2
        flow2 << [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1)] * 4 +
                [getSecondFlow(flow1SwitchPair, flow2SwitchPair, flow1, true)] * 2
        flow3 << [
                getConflictingFlow(flow1SwitchPair, flow1, "source", changePropertyValue(
                        changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber), "vlanId",
                        flow2.destination.vlanId)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination", changePropertyValue(
                        changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber), "vlanId",
                        flow2.source.vlanId)),

                getConflictingFlow(flow1SwitchPair, flow1, "source",
                        changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination",
                        changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId)),

                getConflictingFlow(flow1SwitchPair, flow1, "source",
                        changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber)),
                getConflictingFlow(flow1SwitchPair, flow1, "destination",
                        changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber))
        ]
        [flow1Src, flow1Dst, flow2Src, flow2Dst] << [
                [changePropertyValue(changePropertyValue(flow1.source, "portNumber",
                        flow2.destination.portNumber), "vlanId", flow2.destination.vlanId), flow1.destination,
                 flow2.source, changePropertyValue(changePropertyValue(flow2.destination, "portNumber",
                        flow1.source.portNumber), "vlanId", flow1.source.vlanId)],
                [flow1.source, changePropertyValue(changePropertyValue(flow1.destination, "portNumber",
                        flow2.source.portNumber), "vlanId", flow2.source.vlanId),
                 changePropertyValue(changePropertyValue(flow2.source, "portNumber",
                         flow1.destination.portNumber), "vlanId", flow1.destination.vlanId), flow2.destination],

                [changePropertyValue(flow1.source, "vlanId", flow2.destination.vlanId), flow1.destination,
                 flow2.source, changePropertyValue(flow2.destination, "vlanId", flow1.source.vlanId)],
                [flow1.source, changePropertyValue(flow1.destination, "vlanId", flow2.source.vlanId),
                 changePropertyValue(flow2.source, "vlanId", flow1.destination.vlanId), flow2.destination],

                [changePropertyValue(flow1.source, "portNumber", flow2.destination.portNumber), flow1.destination,
                 flow2.source, changePropertyValue(flow2.destination, "portNumber", flow1.source.portNumber)],
                [flow1.source, changePropertyValue(flow1.destination, "portNumber", flow2.source.portNumber),
                 changePropertyValue(flow2.source, "portNumber", flow1.destination.portNumber), flow2.destination]
        ]
        conflictingEndpoint << ["source", "destination"] * 3
    }

    @Unroll
    def "Unable to swap endpoints for two flows (#description)"() {
        given: "Two active flows"
        flowHelper.addFlow(flow1)
        flowHelper.addFlow(flow2)

        when: "Try to swap endpoints for two flows"
        northbound.swapFlowEndpoint(
                new SwapFlowPayload(flow1.id, flowHelper.toFlowEndpointV2(flow1Src),
                        flowHelper.toFlowEndpointV2(flow1Dst)),
                new SwapFlowPayload(flow2.id, flowHelper.toFlowEndpointV2(flow2Src),
                        flowHelper.toFlowEndpointV2(flow2Dst)))

        then: "An error is received (400 code)"
        def exc = thrown(HttpClientErrorException)
        exc.rawStatusCode == 400
        exc.responseBodyAsString.to(MessageError).errorMessage == "Can not swap endpoints for flows: " +
                "New requested endpoint for '$flow2.id' conflicts with existing endpoint for '$flow1.id'"
{'fraction_non_alphanumeric': 0.0996139516849576, 'fraction_numerical': 0.02484869940491242, 'mean_word_length': 2.848199805384366, 'pattern_counts': {'":': 0, '<': 289, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2659889', 'n_tokens_mistral': 17202, 'n_tokens_neox': 16501, 'n_words': 3382}