from .endpoints import Endpoints
from .instance import Instance
import pytest

class ComponentTest:
    """Execution of common test patterns for a component.

    ComponentTest provides standardized tests for all
    CRUDL operations, specified as endpoints for the
    component.
    In addition, arbitrary endpoints can be tested
    with custom validation methods.

    Parameters
    ----------
    comp: Component object
        Component to execute tests for

    Examples
    --------
    comp = ...
    test = ComponentTest(comp)
    test.test_create(2)
    test.test_update_invalid()
    test.test_update_valid(3)
    """
    def __init__(self, comp):
        self.comp = comp

    def test_create_valid(self, count=1, exp_props=None):
        """Test the success and result of valid input to the create endpoint.

        This test creates instances of the component using
        valid input data, generated by the component.

        For each component instance, it checks if all properties,
        specified in valids, are also present in the endpoints
        result and their values match.
        You can limit the set of tested properties by specifying a list
        of expected properties (exp_props), specified by their name.
        """
        print("# # # test create (valid)")
        for valids in self.comp.get_valids(Endpoints.CREATE, count):
            exp_props_ = [prop.name for prop in self.comp.props.props if prop.spec_name in valids] if exp_props is None else exp_props
            print("valids:", valids)
            with Instance(self.comp, **valids) as instance:
                for name in exp_props_:
                    print("   ", name)
                    p = self.comp.props.get_prop(name)
                    assert self.comp.endpoints.res_has_prop(instance.data, p.res_name)
                    assert valids[p.spec_name] == self.comp.endpoints.prop_from_res(instance.data, p.res_name)

    def test_create_invalid(self, count=1):
        """Test the success of invalid input to the create endpoint.

        This test attempts to create instance of the component
        using invalid data, generated by the component.

        For each creation attempt, it checks if the invalid creation
        indeed caused raising an exception.
        """
        print("# # # test create (invalid)")
        for invalids in self.comp.get_invalids(Endpoints.CREATE, count):
            print("invalids:", invalids)
            with pytest.raises(Exception) as e_info:
                if self.comp.parent is None:
                    self.comp.endpoints.create(invalids, None)
                else:
                    with Instance(self.comp.parent) as parent:
                        self.comp.endpoints.create(invalids, parent)

    def test_create(self, count=1, exp_props=None):
        """Test the success and result of valid and invalid input to the create endpoint."""
        self.test_create_valid(count, exp_props)
        self.test_create_invalid(count)

    def test_update_valid(self, count=1, exp_props=None):
        """Test the success and result of valid input to the update endpoint.

        This test updates properties of an instance and validates
        that the updated and rerturned values are the same as the
        ones specified in valid payload data.
        You can limit the set of tested properties by specifying a list
        of expected property (exp_props), specified by their name.
        """
        print("# # # test update (valid)")
        for valids in self.comp.get_valids(Endpoints.UPDATE, count):
            exp_props_ = [prop.name for prop in self.comp.props.props if prop.spec_name in valids] if exp_props is None else exp_props
            print("valids:", valids)
            with Instance(self.comp) as instance:
                data = self.comp.endpoints.update(valids, instance)
                for name in exp_props_:
                    print("   ", name)
                    p = self.comp.props.get_prop(name)
                    assert self.comp.endpoints.res_has_prop(data, p.res_name)
                    assert valids[p.spec_name] == self.comp.endpoints.prop_from_res(data, p.res_name)

    def test_update_invalid(self, count=1):
        """Test the success and result of invalid input to the update endpoint.

        This test checks if the update endpoint indeed raises an exception
        if the provided data is invalid.
        """
        print("# # # test update (invalid)")
        for invalids in self.comp.get_invalids(Endpoints.UPDATE, count):
            print("invalids:", invalids)
            with Instance(self.comp) as instance:
                with pytest.raises(Exception) as e_info:
                    data = self.comp.endpoints.update(valids, instance)

    def test_update(self, count=1, exp_props=None):
        """Test the success and result of valid and invalid input to the update endpoint."""
        self.test_update_valid(count, exp_props)
        self.test_update_invalid(count)

    def test_get(self, count=1, times=2):
        """Test the success and result of the get endpoint.

        This test checks if the data of a component, obtained using the
        get endpoint, is the same as the one used during its creation.
        """
        print("# # # test get")
        for _ in range(times):
            with Instance(self.comp) as instance:
                print(instance.data)
                for valids in self.comp.get_valids(Endpoints.GET, count):
                    data = self.comp.endpoints.get(valids, instance)
                    for prop in self.comp.props.props:
                        k = prop.res_name
                        v = self.comp.endpoints.prop_from_res(instance.data, k)
                        assert self.comp.endpoints.res_has_prop(data, k)
                        assert v == self.comp.endpoints.prop_from_res(data, k)

    def test_list(self, count=1, entries=5, len_from_res=len):
        """Test the success of the list endpoint.

        This tests checks the count of entries obtained using the
        list endpoints.
        This is done by creating entries components and checking
        if the number of entries in list is actually increased
        accordingly.

        Parameters
        ----------
        count: int, default 1
            Count passed down to the valid data generator
        entries: int, default 5
            Number of entries to create during the test
        len_from_res: callable, default len
            Function to obtain the current component count from
            the list endpoint's result
        """
        print("# # # test list")
        for valids in self.comp.get_valids(Endpoints.LIST, count):
            initial = len_from_res(self.comp.endpoints.list(valids))
            print("initial:", initial)
            elements = []
            for i in range(entries):
                elements.append(Instance(self.comp, None))
                elements[-1].__enter__()
                current = len_from_res(self.comp.endpoints.list(valids))
                print("->", current)
                assert current == initial + i + 1
            for i in range(entries):
                elements[i].__exit__(None, None, None)
                current = len_from_res(self.comp.endpoints.list(valids))
                print("->", current)
                assert current == initial + entries - i - 1

        assert len(self.comp.endpoints.list(valids)) == initial

    def test_valid(self, key, count=1, val_res=None):
        """Test valid input to a non-standard endpoint.

        The custom endpoint is executed for all valid data
        in comp.get_valids(count).
        The result of this request is then validated using
        the custom function.

        Parameters
        ----------
        key: str
            Key to identify the endpoint (as specified
            during initialization of the Endpoints object)
        count: int, default 1
            Count passed down to the valid data generator
        val_res: callable, default None
            Function to validate the result from the
            custom endpoint
            It should return True is the result data is
            valid and False otherwise.
        """
        print("# # # test {} (valid)".format(key))
        for valids in self.comp.get_valids(key, count):
            print("valids:", valids)
            res = self.comp.endpoints.others[key](valids)
            print("        ->", res)
            if val_res is not None:
                assert(val_res(valids, res))

    def test_invalid(self, key, count=1):
        """Test invalid input to a non-standard endpoint.

        The custom endpoint is executed for all invalid data
        in comp.get_invalids(count).
        Then, the test checks if the endpoint's execution
        indeed raises an exception.

        Parameters
        ----------
        key: str
            Key to identify the endpoint (as specified
            during initialization of the Endpoints object)
        count: int, default 1
            Count passed down to the valid data generator
        """
        print("# # # test {} (invalid)".format(key))
        for invalids in self.comp.get_invalids(key, count):
            print("invalids:", invalids)
            with pytest.raises(Exception) as e_info:
                res = self.comp.endpoints.others[key](invalids)

    def test(self, key, count=1, val_res=None):
        """Test valid and invalid input to a non-standard endpoint."""
        self.test_valid(key, count, val_res)
        self.test_invalid(key, count)



import pytest
from .util import rand_int, rand_str, rand_uuid
from .property import Property
from .properties import Properties
from .endpoints import Endpoints
from .component import Component

def test_constr():
    p1 = Property("p1", rand_str, rand_int)
    p2 = Property("p2", rand_int, rand_str)
    def create(values, parent):
        return values
    def delete(values, instance):
        return instance.data
    endpoints = Endpoints(create=create, delete=delete)
    comp = Component("c1", Properties(p1, p2), endpoints)
    comp_test = ComponentTest(comp)
