import asyncio
from contextlib import contextmanager

import pytest

from emmett_core.http.helpers import abort
from emmett_core.http.response import HTTPResponse
from emmett_core.parsers import Parsers
from emmett_core.pipeline import Pipe
from emmett_core.serializers import Serializers


json_load = Parsers.get_for("json")
json_dump = Serializers.get_for("json")


class PipeException(Exception):
    def __init__(self, pipe):
        self.pipe = pipe


class FlowStorePipe(Pipe):
    def __init__(self, current):
        self.current = current

    @property
    def linear_storage(self):
        return self.current._pipeline_linear_storage

    @property
    def parallel_storage(self):
        return self.current._pipeline_parallel_storage

    def store_linear(self, status):
        self.linear_storage.append(self.__class__.__name__ + "." + status)

    def store_parallel(self, status):
        self.parallel_storage.append(self.__class__.__name__ + "." + status)

    async def on_pipe_success(self):
        self.store_linear("success")

    async def on_pipe_failure(self):
        self.store_linear("failure")


class FlowStorePipeCommon(FlowStorePipe):
    async def open(self):
        self.store_parallel("open")

    async def close(self):
        self.store_parallel("close")

    async def pipe(self, next_pipe, **kwargs):
        self.store_linear("pipe")
        return await next_pipe(**kwargs)


class FlowStorePipeSplit(FlowStorePipe):
    async def open_request(self):
        self.store_parallel("open_request")

    async def open_ws(self):
        self.store_parallel("open_ws")

    async def close_request(self):
        self.store_parallel("close_request")

    async def close_ws(self):
        self.store_parallel("close_ws")

    async def pipe_request(self, next_pipe, **kwargs):
        self.store_linear("pipe_request")
        return await next_pipe(**kwargs)

    async def pipe_ws(self, next_pipe, **kwargs):
        self.store_linear("pipe_ws")
        return await next_pipe(**kwargs)


class Pipe1(FlowStorePipeCommon):
    pass


class Pipe2(FlowStorePipeSplit):
    async def pipe_request(self, next_pipe, **kwargs):
        self.store_linear("pipe_request")
        if self.current.request.query_params.skip:
            return "block"
        return await next_pipe(**kwargs)

    async def pipe_ws(self, next_pipe, **kwargs):
        self.store_linear("pipe_ws")
        if self.current.websocket.query_params.skip:
            return
        await next_pipe(**kwargs)


class Pipe3(FlowStorePipeCommon):
    async def open(self):
        await asyncio.sleep(0.05)
        await super().open()


class Pipe4(FlowStorePipeCommon):
    async def close(self):
        await asyncio.sleep(0.05)
        await super().close()


class Pipe5(FlowStorePipeCommon):
    pass


class Pipe6(FlowStorePipeCommon):
    pass


class Pipe7(FlowStorePipeCommon):
    pass


class ExcPipeOpen(FlowStorePipeCommon):
    async def open(self):
        raise PipeException(self)


class ExcPipeClose(FlowStorePipeCommon):
    async def close(self):
        raise PipeException(self)


class PipeSR1(FlowStorePipeSplit):
    def on_receive(self, data):
        data = json_load(data)
        return dict(pipe1r="receive_inject", **data)

    def on_send(self, data):
        return json_dump(dict(pipe1s="send_inject", **data))


class PipeSR2(FlowStorePipeSplit):
    def on_receive(self, data):
        return dict(pipe2r="receive_inject", **data)

    def on_send(self, data):
        return dict(pipe2s="send_inject", **data)


class CTXWrapper:
    def __init__(self, ctx, router):
        self.ctx = ctx
        self.router = router

    def dispatch(self):
        return self.router.dispatch()


class ReqCTXWrapper(CTXWrapper):
    def dispatch(self):
        return self.router.dispatch(self.ctx.request, self.ctx.response)


class WSCTXWrapper(CTXWrapper):
    def dispatch(self):
        return self.router.dispatch(self.ctx.websocket)


def linear_flows_are_equal(flow, ctx):
    try:
        for index, value in enumerate(flow):
            if ctx._pipeline_linear_storage[index] != value:
                return False
    except Exception:
        return False
    return True


def parallel_flows_are_equal(flow, ctx):
    return set(flow) == set(ctx._pipeline_parallel_storage)


@pytest.fixture(scope="function")
def pipeline_http_ctx(http_ctx_builder):
    @contextmanager
    def ctx_builder(*args, **kwargs):
        with http_ctx_builder(*args, *kwargs) as _ctx:
            _ctx.ctx._pipeline_generic_storage = []
            _ctx.ctx._pipeline_linear_storage = []
            _ctx.ctx._pipeline_parallel_storage = []
            yield ReqCTXWrapper(_ctx.ctx, _ctx.ctx.app._router_http)

    return ctx_builder


@pytest.fixture(scope="function")
def pipeline_ws_ctx(ws_ctx_builder):
    @contextmanager
    def ctx_builder(*args, **kwargs):
        with ws_ctx_builder(*args, *kwargs) as _ctx:
            _ctx.ctx._pipeline_generic_storage = []
            _ctx.ctx._pipeline_linear_storage = []
            _ctx.ctx._pipeline_parallel_storage = []
            _ctx.ctx._receive_storage = []
            _ctx.ctx._send_storage = []
            yield WSCTXWrapper(_ctx.ctx, _ctx.ctx.app._router_ws)

    return ctx_builder


@pytest.fixture(scope="function", autouse=True)
def pipeline_app(current, app):
    app.pipeline = [Pipe1(current), Pipe2(current), Pipe3(current)]

    @app.route()
    def ok():
        return "ok"

    @app.route()
    def http_error():
        abort(current, 422)

    @app.route()
    def error():
        raise Exception

    @app.route(pipeline=[ExcPipeOpen(current), Pipe4(current)])
    def open_error():
        return ""

    @app.route(pipeline=[ExcPipeClose(current), Pipe4(current)])
    def close_error():
        return ""

    @app.route(pipeline=[Pipe4(current)])
    def pipe4():
        return "4"

    @app.websocket()
    async def ws_ok():
        await current.websocket.send("ok")

    @app.websocket()
    def ws_error():
        raise Exception

    @app.websocket(pipeline=[ExcPipeOpen(current), Pipe4(current)])
    def ws_open_error():
        return

    @app.websocket(pipeline=[ExcPipeClose(current), Pipe4(current)])
    def ws_close_error():
        return

    @app.websocket(pipeline=[Pipe4(current)])
    def ws_pipe4():
        return

    @app.websocket(pipeline=[PipeSR1(current), PipeSR2(current)])
    async def ws_inject():
        data = await current.websocket.receive()
        current._receive_storage.append(data)
        await current.websocket.send(data)

    mod = app.module(__name__, "mod", url_prefix="mod")
    mod.pipeline = [Pipe5(current)]

    @mod.route()
    def pipe5():
        return "5"

    @mod.route(pipeline=[Pipe6(current)])
    def pipe6():
        return "6"

    @mod.websocket()
    def ws_pipe5():
        return

    @mod.websocket(pipeline=[Pipe6(current)])
    def ws_pipe6():
        return

    mg1 = app.module(__name__, "mg1", url_prefix="mg1")
    mg2 = app.module(__name__, "mg2", url_prefix="mg2")
    mg1.pipeline = [Pipe5(current)]
    mg2.pipeline = [Pipe6(current)]
    mg = app.module_group(mg1, mg2)

    @mg.route()
    async def pipe_mg():
        return "mg"

    mgc = mg.module(__name__, "mgc", url_prefix="mgc")
    mgc.pipeline = [Pipe7(current)]

    @mgc.route()
    async def pipe_mgc():
        return "mgc"

    return app


@pytest.mark.asyncio
async def test_ok_flow(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/ok") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/ws_ok") as ctx:
        parallel_flow = ["Pipe1.open", "Pipe2.open_ws", "Pipe3.open", "Pipe3.close", "Pipe2.close_ws", "Pipe1.close"]
        linear_flow = ["Pipe1.pipe", "Pipe2.pipe_ws", "Pipe3.pipe", "Pipe3.success", "Pipe2.success", "Pipe1.success"]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_httperror_flow(pipeline_http_ctx):
    with pipeline_http_ctx("/http_error") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        try:
            await ctx.dispatch()
        except HTTPResponse:
            pass
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_error_flow(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/error") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe3.failure",
            "Pipe2.failure",
            "Pipe1.failure",
        ]
        try:
            await ctx.dispatch()
        except Exception:
            pass
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/ws_error") as ctx:
        parallel_flow = ["Pipe1.open", "Pipe2.open_ws", "Pipe3.open", "Pipe3.close", "Pipe2.close_ws", "Pipe1.close"]
        linear_flow = ["Pipe1.pipe", "Pipe2.pipe_ws", "Pipe3.pipe", "Pipe3.failure", "Pipe2.failure", "Pipe1.failure"]
        try:
            await ctx.dispatch()
        except Exception:
            pass
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_open_error(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/open_error") as ctx:
        parallel_flow = ["Pipe1.open", "Pipe2.open_request", "Pipe3.open", "Pipe4.open"]
        linear_flow = []
        try:
            await ctx.dispatch()
        except PipeException as e:
            assert isinstance(e.pipe, ExcPipeOpen)
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/ws_open_error") as ctx:
        parallel_flow = ["Pipe1.open", "Pipe2.open_ws", "Pipe3.open", "Pipe4.open"]
        linear_flow = []
        try:
            await ctx.dispatch()
        except PipeException as e:
            assert isinstance(e.pipe, ExcPipeOpen)
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_close_error(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/close_error") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "ExcPipeClose.open",
            "Pipe4.open",
            "Pipe4.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "ExcPipeClose.pipe",
            "Pipe4.pipe",
            "Pipe4.success",
            "ExcPipeClose.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        try:
            await ctx.dispatch()
        except PipeException as e:
            assert isinstance(e.pipe, ExcPipeClose)
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/ws_close_error") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_ws",
            "Pipe3.open",
            "ExcPipeClose.open",
            "Pipe4.open",
            "Pipe4.close",
            "Pipe3.close",
            "Pipe2.close_ws",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_ws",
            "Pipe3.pipe",
            "ExcPipeClose.pipe",
            "Pipe4.pipe",
            "Pipe4.success",
            "ExcPipeClose.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        try:
            await ctx.dispatch()
        except PipeException as e:
            assert isinstance(e.pipe, ExcPipeClose)
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_flow_interrupt(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/ok?skip=yes") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = ["Pipe1.pipe", "Pipe2.pipe_request", "Pipe2.success", "Pipe1.success"]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/ws_ok?skip=yes") as ctx:
        parallel_flow = ["Pipe1.open", "Pipe2.open_ws", "Pipe3.open", "Pipe3.close", "Pipe2.close_ws", "Pipe1.close"]
        linear_flow = ["Pipe1.pipe", "Pipe2.pipe_ws", "Pipe2.success", "Pipe1.success"]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_pipeline_composition(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/pipe4") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe4.open",
            "Pipe4.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe4.pipe",
            "Pipe4.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/ws_pipe4") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_ws",
            "Pipe3.open",
            "Pipe4.open",
            "Pipe4.close",
            "Pipe3.close",
            "Pipe2.close_ws",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_ws",
            "Pipe3.pipe",
            "Pipe4.pipe",
            "Pipe4.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_module_pipeline(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/mod/pipe5") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe5.open",
            "Pipe5.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe5.pipe",
            "Pipe5.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/mod/ws_pipe5") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_ws",
            "Pipe3.open",
            "Pipe5.open",
            "Pipe5.close",
            "Pipe3.close",
            "Pipe2.close_ws",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_ws",
            "Pipe3.pipe",
            "Pipe5.pipe",
            "Pipe5.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_module_pipeline_composition(pipeline_http_ctx, pipeline_ws_ctx):
    with pipeline_http_ctx("/mod/pipe6") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe5.open",
            "Pipe6.open",
            "Pipe6.close",
            "Pipe5.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe5.pipe",
            "Pipe6.pipe",
            "Pipe6.success",
            "Pipe5.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_ws_ctx("/mod/ws_pipe6") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_ws",
            "Pipe3.open",
            "Pipe5.open",
            "Pipe6.open",
            "Pipe6.close",
            "Pipe5.close",
            "Pipe3.close",
            "Pipe2.close_ws",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_ws",
            "Pipe3.pipe",
            "Pipe5.pipe",
            "Pipe6.pipe",
            "Pipe6.success",
            "Pipe5.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_module_group_pipeline(pipeline_http_ctx):
    with pipeline_http_ctx("/mg1/pipe_mg") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe5.open",
            "Pipe5.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe5.pipe",
            "Pipe5.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_http_ctx("/mg2/pipe_mg") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe6.open",
            "Pipe6.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe6.pipe",
            "Pipe6.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_module_group_pipeline_composition(pipeline_http_ctx):
    with pipeline_http_ctx("/mg1/mgc/pipe_mgc") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe5.open",
            "Pipe7.open",
            "Pipe7.close",
            "Pipe5.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe5.pipe",
            "Pipe7.pipe",
            "Pipe7.success",
            "Pipe5.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

    with pipeline_http_ctx("/mg2/mgc/pipe_mgc") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_request",
            "Pipe3.open",
            "Pipe6.open",
            "Pipe7.open",
            "Pipe7.close",
            "Pipe6.close",
            "Pipe3.close",
            "Pipe2.close_request",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_request",
            "Pipe3.pipe",
            "Pipe6.pipe",
            "Pipe7.pipe",
            "Pipe7.success",
            "Pipe6.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)


@pytest.mark.asyncio
async def test_receive_send_flow(pipeline_ws_ctx):
    with pipeline_ws_ctx("/ws_inject") as ctx:
        parallel_flow = [
            "Pipe1.open",
            "Pipe2.open_ws",
            "Pipe3.open",
            "PipeSR1.open_ws",
            "PipeSR2.open_ws",
            "PipeSR2.close_ws",
            "PipeSR1.close_ws",
            "Pipe3.close",
            "Pipe2.close_ws",
            "Pipe1.close",
        ]
        linear_flow = [
            "Pipe1.pipe",
            "Pipe2.pipe_ws",
            "Pipe3.pipe",
            "PipeSR1.pipe_ws",
            "PipeSR2.pipe_ws",
            "PipeSR2.success",
            "PipeSR1.success",
            "Pipe3.success",
            "Pipe2.success",
            "Pipe1.success",
        ]
        await ctx.dispatch()
        assert linear_flows_are_equal(linear_flow, ctx.ctx)
        assert parallel_flows_are_equal(parallel_flow, ctx.ctx)

        assert ctx.ctx._receive_storage[-1] == {"pipe1r": "receive_inject", "pipe2r": "receive_inject"}
        assert json_load(ctx.ctx._send_storage[-1]) == {
            "pipe1r": "receive_inject",
            "pipe2r": "receive_inject",
            "pipe1s": "send_inject",
            "pipe2s": "send_inject",
        }
