# Path: pypeman/contrib/ctx.py
# class CombineCtx(nodes.BaseNode):
#     """
#     creates payload dict with combination of different contexts
#     """
# 
#     def __init__(self, ctx_names, meta_from=None, flatten=False,  *args, **kwargs):
#         """
#             param ctx_names: list of context names to save or dict with mapping
#                    of context name to payload keys
#             param: flatten: if true all ctx payloads (must be dicts) will be combined
#                     in one dict
#             param: meta_from: specifies which meta the resulting message should have
#         """
#         super().__init__(*args, **kwargs)
#         if not isinstance(ctx_names, dict):
#             if flatten:
#                 ctx_dst = len(ctx_names) * [None]
#             else:
#                 ctx_dst = ctx_names
#             ctx_names = dict(zip(ctx_names, ctx_dst))
#         self.ctx_names = ctx_names
#         if len(ctx_names) < 2:
#             raise NodeException("must have at least two contexts for combining")
#         if meta_from is None:
#             meta_from = next(iter(ctx_names.keys()))
#         self.meta_from = meta_from
# 
#     def process(self, msg):
#         payload = {}
#         ch_logger = self.channel.logger
#         ch_logger.debug(
#             "combine ctx_names = %s / meta from %r",
#             repr(self.ctx_names),
#             self.meta_from,
#             )
#         # TODO: can we just copy the meta if the payload changed?
#         # TODO: If not, then rmv meta_from param and create new meta
#         msg.meta = msg.ctx[self.meta_from]['meta']
# 
#         for ctx_name, dst in self.ctx_names.items():
#             ch_logger.debug("combine ctx = %s -> %s", ctx_name, dst)
#             if dst is None:
#                 ctx_payload = msg.ctx[ctx_name]['payload']
#                 ch_logger.debug(
#                     "upd payload with ctx payload of %s ( %s )",
#                     ctx_name, repr(ctx_payload))
#                 payload.update(ctx_payload)
#             else:
#                 payload[dst] = msg.ctx[ctx_name]['payload']
# 
#         msg.payload = payload
# 
#         return msg
# 
# Path: pypeman/nodes.py
# class NodeException(Exception):
#     """ custom exception """
# 
# Path: pypeman/tests/pytest_helpers.py
# @pytest.fixture(scope="function")
# def clear_graph():
#     """
#     ensure, that before and after a pypeman test
#     the pypeman graph is entirely cleared
#     """
#     n_nodes = len(pypeman.nodes.all_nodes)
#     n_channels = len(pypeman.channels.all_channels)
#     n_endpoints = len(pypeman.endpoints.all_endpoints)
#     pypeman.nodes.reset_pypeman_nodes()
#     pypeman.channels.reset_pypeman_channels()
#     pypeman.endpoints.reset_pypeman_endpoints()
#     yield n_nodes, n_channels, n_endpoints
#     pypeman.nodes.reset_pypeman_nodes()
#     pypeman.channels.reset_pypeman_channels()
#     pypeman.endpoints.reset_pypeman_endpoints()
# 
# Path: pypeman/tests/common.py
# def generate_msg(timestamp=None, message_content=default_message_content,
#                  message_meta=None, with_context=False):
#     """ generates a default message """
#     m = message.Message()
#     if timestamp:
#         if isinstance(timestamp, tuple):
#             m.timestamp = datetime.datetime(*timestamp)
#         else:  # assume it's a datetime object
#             m.timestamp = timestamp
#     else:  # just use current time
#         m.timestamp = datetime.datetime.utcnow()
# 
#     m.payload = message_content
# 
#     if message_meta is None:
#         m.meta = dict(default_message_meta)
#     else:
#         m.meta = message_meta
# 
#     if with_context:
#         # Add context message
#         mctx = generate_msg(message_content={'question': 'known'}, message_meta={'answer': 43})
#         m.add_context('test', mctx)
# 
#     return m
# 
# Path: pypeman/tests/test_nodes.py
# class FakeChannel():
#     def __init__(self, loop):
#         self.logger = mock.MagicMock()
#         self.uuid = 'fakeChannel'
#         self.name = 'fakeChannel'
#         self.parent_uids = "parent_uid"
#         self.parent_names = ["parent_names"]
# 
#         self.loop = loop


import asyncio
import pytest
from pypeman.contrib.ctx import CombineCtx
from pypeman.nodes import NodeException
from pypeman.tests.pytest_helpers import clear_graph  # noqa: F401
from pypeman.tests.common import generate_msg
from pypeman.tests.test_nodes import FakeChannel


@pytest.mark.usefixtures("clear_graph")
def test_combine_ctx_not_two_names(event_loop):
    with pytest.raises(NodeException):
        CombineCtx([], name="ctx1")
    with pytest.raises(NodeException):
        CombineCtx(["a"], name="ctx2")


@pytest.mark.usefixtures("clear_graph")
def test_combine_ctx_2_names(event_loop):
    loop = event_loop
    asyncio.set_event_loop(loop)
    # nut == Node Under Test
    nut = CombineCtx(["a", "b"], name="ctx1")
    nut.channel = FakeChannel(loop)
    msg, ctx_msgs = mk_msgs_w_ctx("a", "b")

    rslt = loop.run_until_complete(nut.handle(msg))
    assert rslt.payload["a"] == ctx_msgs[0].payload
    assert rslt.payload["b"] == ctx_msgs[1].payload
    assert rslt.meta == ctx_msgs[0].meta


@pytest.mark.usefixtures("clear_graph")
def test_combine_ctx_2_names_w_meta(event_loop):
    loop = event_loop
    asyncio.set_event_loop(loop)
    # nut == Node Under Test

# Path: pypeman/contrib/ctx.py
# class CombineCtx(nodes.BaseNode):
#     """
#     creates payload dict with combination of different contexts
#     """
# 
#     def __init__(self, ctx_names, meta_from=None, flatten=False,  *args, **kwargs):
#         """
#             param ctx_names: list of context names to save or dict with mapping
#                    of context name to payload keys
#             param: flatten: if true all ctx payloads (must be dicts) will be combined
#                     in one dict
#             param: meta_from: specifies which meta the resulting message should have
#         """
#         super().__init__(*args, **kwargs)
#         if not isinstance(ctx_names, dict):
#             if flatten:
#                 ctx_dst = len(ctx_names) * [None]
#             else:
#                 ctx_dst = ctx_names
#             ctx_names = dict(zip(ctx_names, ctx_dst))
#         self.ctx_names = ctx_names
#         if len(ctx_names) < 2:
#             raise NodeException("must have at least two contexts for combining")
#         if meta_from is None:
#             meta_from = next(iter(ctx_names.keys()))
#         self.meta_from = meta_from
# 
#     def process(self, msg):
#         payload = {}
#         ch_logger = self.channel.logger
#         ch_logger.debug(
#             "combine ctx_names = %s / meta from %r",
#             repr(self.ctx_names),
#             self.meta_from,
#             )
#         # TODO: can we just copy the meta if the payload changed?
#         # TODO: If not, then rmv meta_from param and create new meta
#         msg.meta = msg.ctx[self.meta_from]['meta']
# 
#         for ctx_name, dst in self.ctx_names.items():
#             ch_logger.debug("combine ctx = %s -> %s", ctx_name, dst)
#             if dst is None:
#                 ctx_payload = msg.ctx[ctx_name]['payload']
#                 ch_logger.debug(
#                     "upd payload with ctx payload of %s ( %s )",
#                     ctx_name, repr(ctx_payload))
#                 payload.update(ctx_payload)
#             else:
#                 payload[dst] = msg.ctx[ctx_name]['payload']
# 
#         msg.payload = payload
# 
#         return msg
# 
# Path: pypeman/nodes.py
# class NodeException(Exception):
#     """ custom exception """
# 
# Path: pypeman/tests/pytest_helpers.py
# @pytest.fixture(scope="function")
# def clear_graph():
#     """
#     ensure, that before and after a pypeman test
#     the pypeman graph is entirely cleared
#     """
#     n_nodes = len(pypeman.nodes.all_nodes)
#     n_channels = len(pypeman.channels.all_channels)
#     n_endpoints = len(pypeman.endpoints.all_endpoints)
#     pypeman.nodes.reset_pypeman_nodes()
#     pypeman.channels.reset_pypeman_channels()
#     pypeman.endpoints.reset_pypeman_endpoints()
#     yield n_nodes, n_channels, n_endpoints
#     pypeman.nodes.reset_pypeman_nodes()
#     pypeman.channels.reset_pypeman_channels()
#     pypeman.endpoints.reset_pypeman_endpoints()
# 
# Path: pypeman/tests/common.py
# def generate_msg(timestamp=None, message_content=default_message_content,
#                  message_meta=None, with_context=False):
#     """ generates a default message """
#     m = message.Message()
#     if timestamp:
#         if isinstance(timestamp, tuple):
#             m.timestamp = datetime.datetime(*timestamp)
#         else:  # assume it's a datetime object
#             m.timestamp = timestamp
#     else:  # just use current time
#         m.timestamp = datetime.datetime.utcnow()
# 
#     m.payload = message_content
# 
#     if message_meta is None:
#         m.meta = dict(default_message_meta)
#     else:
#         m.meta = message_meta
# 
#     if with_context:
#         # Add context message
#         mctx = generate_msg(message_content={'question': 'known'}, message_meta={'answer': 43})
#         m.add_context('test', mctx)
# 
#     return m
# 
# Path: pypeman/tests/test_nodes.py
# class FakeChannel():
#     def __init__(self, loop):
#         self.logger = mock.MagicMock()
#         self.uuid = 'fakeChannel'
#         self.name = 'fakeChannel'
#         self.parent_uids = "parent_uid"
#         self.parent_names = ["parent_names"]
# 
#         self.loop = loop


# Path: pypeman/tests/test_ctx_nodes.py
import asyncio
import pytest
from pypeman.contrib.ctx import CombineCtx
from pypeman.nodes import NodeException
from pypeman.tests.pytest_helpers import clear_graph  # noqa: F401
from pypeman.tests.common import generate_msg
from pypeman.tests.test_nodes import FakeChannel



@pytest.mark.usefixtures("clear_graph")
def test_combine_ctx_not_two_names(event_loop):
    with pytest.raises(NodeException):
        CombineCtx([], name="ctx1")
    with pytest.raises(NodeException):
        CombineCtx(["a"], name="ctx2")


@pytest.mark.usefixtures("clear_graph")
def test_combine_ctx_2_names(event_loop):
    loop = event_loop
    asyncio.set_event_loop(loop)
    # nut == Node Under Test
    nut = CombineCtx(["a", "b"], name="ctx1")
    nut.channel = FakeChannel(loop)
    msg, ctx_msgs = mk_msgs_w_ctx("a", "b")

    rslt = loop.run_until_complete(nut.handle(msg))
    assert rslt.payload["a"] == ctx_msgs[0].payload
    assert rslt.payload["b"] == ctx_msgs[1].payload
    assert rslt.meta == ctx_msgs[0].meta


@pytest.mark.usefixtures("clear_graph")
def test_combine_ctx_2_names_w_meta(event_loop):
    loop = event_loop
    asyncio.set_event_loop(loop)
    # nut == Node Under Test

    nut = CombineCtx(["a", "b"], meta_from="b", name="ctx1")