Exemplo n.º 1
0
def test_message_nested_sync_input(client):
    data = {"dictionary": {"name": "test"}}
    message = {
        "actionQueue": [{
            "payload": {
                "name": "dictionary.name",
                "value": "test1"
            },
            "type": "syncInput",
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        shortuuid.uuid()[:8],
        "epoch":
        time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert not body["errors"]
    assert body["data"].get("dictionary") == {"name": "test1"}
Exemplo n.º 2
0
def test_message_single(client, settings):
    _set_serial(settings, True, 5)

    data = {"counter": 0}
    component_id = shortuuid.uuid()[:8]
    message = {
        "actionQueue": [{
            "payload": {
                "name": "slow_action"
            },
            "type": "callMethod",
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        component_id,
        "epoch":
        time.time(),
    }

    response = client.post(
        "/message/tests.views.message.test_call_method_multiple.FakeSlowComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)
    assert body["data"].get("counter") == 1
Exemplo n.º 3
0
def test_generate_checksum_str(settings):
    settings.SECRET_KEY = "asdf"

    expected = "TfxFqcQL"
    actual = generate_checksum('{"name": "test"}')

    assert expected == actual
Exemplo n.º 4
0
def test_nested_setter(client):
    data = {"nested": {"check": False}}
    message = {
        "actionQueue": [
            {
                "type": "callMethod",
                "payload": {
                    "name": "nested.check=True"
                }
            },
        ],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        shortuuid.uuid()[:8],
        "epoch":
        time.time(),
    }

    body = _post_message_and_get_body(client, message)

    assert not body["errors"]
    assert body["data"]["nested"]["check"] == True
Exemplo n.º 5
0
def test_equal_sign(client):
    data = {"nested": {"check": False}, "method_arg": ""}
    message = {
        "actionQueue": [
            {
                "type": "callMethod",
                "payload": {
                    "name": "test_method_string_arg('does=thiswork?')"
                },
            },
        ],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        shortuuid.uuid()[:8],
        "epoch":
        time.time(),
    }

    body = _post_message_and_get_body(client, message)

    assert not body["errors"]
    assert body["data"]["method_arg"] == "does=thiswork?"
Exemplo n.º 6
0
def test_message_call_method_refresh_redirect(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "test_refresh_redirect"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert "redirect" in body
    redirect = body["redirect"]
    assert redirect.get("url") == "/something-here"
    assert redirect.get("refresh")
    assert redirect.get("title") == "new title"
Exemplo n.º 7
0
def test_message_two(client, settings):
    _set_serial(settings, True, 5)

    data = {"counter": 0}
    component_id = shortuuid.uuid()[:8]
    message = {
        "actionQueue": [{
            "payload": {
                "name": "slow_action"
            },
            "type": "callMethod",
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        component_id,
    }
    messages = [(client, 0, message), (client, 0.1, message)]

    with ThreadPool(len(messages)) as pool:
        results = pool.map(_message_runner, messages)
        assert len(results) == len(messages)

        first_result = results[0]
        first_body = orjson.loads(first_result.content)
        assert first_body["data"].get("counter") == 2

        second_result = results[1]
        second_body = orjson.loads(second_result.content)
        assert second_body["queued"] == True
Exemplo n.º 8
0
def test_message_call_method_reset(client):
    data = {"method_count": 1}
    message = {
        "actionQueue": [
            {"payload": {"name": "method_count=2"}, "type": "callMethod"},
            {"payload": {"name": "$reset"}, "type": "callMethod",},
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert body["data"]["method_count"] == 0
    # `data` should contain all data (not just the diffs) for resets
    assert body["data"].get("check") is not None
    assert body["data"].get("dictionary") is not None
Exemplo n.º 9
0
def test_message_call_method_poll_update(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "test_poll_update"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert "poll" in body
    poll = body["poll"]
    assert poll.get("timing") == 1000
    assert poll.get("disable") == True
    assert poll.get("method") == "new_method"
Exemplo n.º 10
0
def test_message_call_method_return_value(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "test_return_value"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert "return" in body
    return_data = body["return"]
    assert return_data.get("method") == "test_return_value"
    assert return_data.get("args") == []
    assert return_data.get("kwargs") == {}
    assert return_data.get("value") == "booya"
Exemplo n.º 11
0
def test_message_multiple_with_updated_data(client, settings):
    """
    Not sure how likely this is to happen, but if the data got changed in a new queued request
    it gets disregarded because no sane way to merge it together. Not ideal, but not sure how to
    handle it.
    """
    _set_serial(settings, True, 5)

    data = {"counter": 0}
    component_id = shortuuid.uuid()[:8]
    message = {
        "actionQueue": [{
            "payload": {
                "name": "slow_action"
            },
            "type": "callMethod",
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        component_id,
    }
    messages = [(client, 0, message), (client, 0.1, message),
                (client, 0.2, message)]

    # This new message with different data won't get used because not
    # sure how to reconcile this with resulting data from queued messages
    message_with_new_data = deepcopy(message)
    message_with_new_data["data"] = {"counter": 7}
    message_with_new_data["checksum"] = generate_checksum(
        orjson.dumps(message_with_new_data["data"]))
    messages.append((client, 0.4, message_with_new_data))

    with ThreadPool(len(messages)) as pool:
        results = pool.map(_message_runner, messages)
        assert len(results) == len(messages)

        first_result = results[0]
        first_body = orjson.loads(first_result.content)
        assert first_body["data"].get("counter") == 4

        for result in results[1:]:
            result = results[1]
            body = orjson.loads(result.content)
            assert body.get("queued") == True
Exemplo n.º 12
0
def test_message_db_input_create(client):
    data = {"flavors": []}

    message = {
        "actionQueue": [
            {
                "payload": {
                    "model": "flavors",
                    "db": {
                        "pk": "",
                        "name": "flavor"
                    },
                    "fields": {
                        "name": "Sugar Browning-Nutty"
                    },
                },
                "type": "dbInput",
            },
            {
                "type": "callMethod",
                "payload": {
                    "name": "$refresh",
                    "params": []
                }
            },
        ],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        "FDHcbzGf",
    }

    assert Flavor.objects.all().count() == 0

    response = client.post(
        "/message/tests.views.fake_components.FakeModelComponent",
        message,
        content_type="application/json",
    )

    flavor = Flavor.objects.get(id=1)
    assert flavor.name == "Sugar Browning-Nutty"

    body = orjson.loads(response.content)

    assert not body["errors"]
    assert body["data"] == {
        "flavors": [{
            "pk": 1,
            "name": "Sugar Browning-Nutty",
            "decimal_value": None,
            "float_value": None,
            "label": "",
            "parent": None,
        }]
    }
Exemplo n.º 13
0
def test_message_db_input_update(client):
    flavor = Flavor(id=1, name="Enzymatic-Flowery")
    flavor.save()
    data = {"flavors": [{"pk": flavor.pk, "title": flavor.name}]}

    message = {
        "actionQueue": [
            {
                "payload": {
                    "model": "flavors",
                    "db": {
                        "pk": flavor.pk,
                        "name": "flavor"
                    },
                    "fields": {
                        "name": "Flowery-Floral"
                    },
                },
                "type": "dbInput",
            },
            {
                "type": "callMethod",
                "payload": {
                    "name": "$refresh",
                    "params": []
                }
            },
        ],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        "FDHcbzGf",
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeModelComponent",
        message,
        content_type="application/json",
    )

    flavor = Flavor.objects.get(id=1)
    assert flavor.name == "Flowery-Floral"

    body = orjson.loads(response.content)

    assert not body["errors"]
    assert body["data"] == {
        "flavors": [{
            "pk": 1,
            "name": "Flowery-Floral",
            "decimal_value": None,
            "float_value": None,
            "label": "",
            "parent": None,
        }]
    }
Exemplo n.º 14
0
    def validate_checksum(self):
        """
        Validates that the checksum in the request matches the data.

        Returns:
            Raises `AssertionError` if the checksums don't match.
        """
        checksum = self.body.get("checksum")
        assert checksum, "Missing checksum"

        generated_checksum = generate_checksum(
            dumps(self.data, fix_floats=False))
        assert checksum == generated_checksum, "Checksum does not match"
Exemplo n.º 15
0
def test_message_nested_toggle(client):
    data = {"nested": {"check": False}}
    message = {
        "actionQueue": [
            {"type": "callMethod", "payload": {"name": "$toggle('nested.check')"}},
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": "FDHcbzGf",
    }

    body = _post_message_and_get_body(client, message)

    assert not body["errors"]
    assert body["data"]["nested"]["check"] == True
Exemplo n.º 16
0
def post_and_get_response(client, url="", data={}, action_queue=[]):
    data = {}
    message = {
        "actionQueue": action_queue,
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        url,
        message,
        content_type="application/json",
    )

    return response
Exemplo n.º 17
0
def test_message_call_method(client):
    data = {}
    message = {
        "actionQueue": [{"payload": {"name": "test_method"}, "type": "callMethod",}],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert body["data"].get("method_count") == 1
Exemplo n.º 18
0
def test_unicorn_render_hash(settings):
    settings.DEBUG = True
    token = Token(
        TokenType.TEXT,
        "unicorn 'tests.templatetags.test_unicorn_render.FakeComponentParent'",
    )
    unicorn_node = unicorn(None, token)
    context = {}
    html = unicorn_node.render(context)

    assert '<script type="module"' in html
    assert len(re.findall('<script type="module"', html)) == 1
    assert '"hash":"' in html

    # Assert that the content hash is correct
    script_idx = html.index("<script")
    rendered_content = html[:script_idx]
    expected_hash = generate_checksum(rendered_content)
    assert f'"hash":"{expected_hash}"' in html
def test_message_call_method_nested_toggle(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "$toggle('nested.check')"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": "FDHcbzGf",
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert body["data"].get("nested").get("check") == True
def test_message_call_method_no_validation(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "set_text_no_validation"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": "FDHcbzGf",
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeValidationComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert not body["errors"]
Exemplo n.º 21
0
def test_message_call_method_nested_setter(client):
    data = {"nested": {"check": True}}
    message = {
        "actionQueue": [
            {"payload": {"name": "nested.check=False"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert body["data"].get("nested").get("check") == False
Exemplo n.º 22
0
def test_message_call_method_hash_update(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "test_hash_update"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
        "epoch": time.time(),
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert "redirect" in body
    assert body["redirect"].get("hash") == "#test=1"
Exemplo n.º 23
0
def test_message_generated_checksum_matches_dom_checksum(client):
    data = {"clicked": False}
    message = {
        "actionQueue": [{
            "payload": {
                "name": "test_method"
            },
            "type": "callMethod",
            "target": None,
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        shortuuid.uuid()[:8],
        "epoch":
        time.time(),
    }

    response = client.post(
        "/message/tests.views.message.test_target.FakeTargetComponent",
        message,
        content_type="application/json",
    )

    body = response.json()
    dom = body.get("dom")

    assert dom
    assert not body.get("partials")
    assert body.get("data", {}).get("clicked") == True

    soup = BeautifulSoup(dom, features="html.parser")

    for element in soup.find_all():
        if "unicorn:checksum" in element.attrs:
            assert element.attrs["unicorn:checksum"] == body.get("checksum")
            break
Exemplo n.º 24
0
def test_message_call_method_validation(client):
    data = {}
    message = {
        "actionQueue": [
            {"payload": {"name": "set_text_with_validation"}, "type": "callMethod",}
        ],
        "data": data,
        "checksum": generate_checksum(orjson.dumps(data)),
        "id": shortuuid.uuid()[:8],
    }

    response = client.post(
        "/message/tests.views.fake_components.FakeValidationComponent",
        message,
        content_type="application/json",
    )

    body = orjson.loads(response.content)

    assert body["errors"]
    assert body["errors"]["number"]
    assert body["errors"]["number"][0]["code"] == "required"
    assert body["errors"]["number"][0]["message"] == "This field is required."
Exemplo n.º 25
0
def test_message_second_request_not_queued_because_dummy_cache(
        client, settings):
    _set_serial(settings,
                True,
                5,
                cache_backend="django.core.cache.backends.dummy.DummyCache")

    data = {"counter": 0}
    component_id = shortuuid.uuid()[:8]
    message = {
        "actionQueue": [{
            "payload": {
                "name": "slow_action"
            },
            "type": "callMethod",
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        component_id,
    }
    messages = [(client, 0, message), (client, 0.2, message)]

    with ThreadPool(len(messages)) as pool:
        results = pool.map(_message_runner, messages)
        assert len(results) == len(messages)

        first_result = results[0]
        first_body = orjson.loads(first_result.content)
        assert first_body["data"].get("counter") == 1

        second_result = results[1]
        second_body = orjson.loads(second_result.content)
        assert second_body["data"].get("counter") == 1
Exemplo n.º 26
0
def test_message_target_key(client):
    data = {"clicked": False}
    message = {
        "actionQueue": [{
            "payload": {
                "name": "test_method"
            },
            "type": "callMethod",
            "partial": {
                "target": "test-target-key"
            },
        }],
        "data":
        data,
        "checksum":
        generate_checksum(orjson.dumps(data)),
        "id":
        shortuuid.uuid()[:8],
        "epoch":
        time.time(),
    }

    response = client.post(
        "/message/tests.views.message.test_target.FakeTargetComponent",
        message,
        content_type="application/json",
    )

    body = response.json()

    assert body.get("dom") is None
    assert len(body["partials"]) == 1
    assert body["partials"][0]["key"] == "test-target-key"
    assert body["partials"][0][
        "dom"] == '<div unicorn:key="test-target-key"></div>'
    assert body.get("data", {}).get("clicked") == True
Exemplo n.º 27
0
def _process_component_request(
    request: HttpRequest, component_request: ComponentRequest
) -> Dict:
    """
    Process a `ComponentRequest`:
        1. construct a Component view
        2. set all of the properties on the view from the data
        3. execute the type
            - update the properties based on the payload for "syncInput"
            - call the method specified for "callMethod"
        4. validate any fields specified in a Django form
        5. construct a `dict` that will get returned in a `JsonResponse` later on

    Args:
        param request: HttpRequest for the function-based view.
        param: component_request: Component request to process.

    Returns:
        `dict` with the following structure:
        {
            "id": component_id,
            "dom": html,  // re-rendered version of the component after actions in the payload are completed
            "data": {},  // updated data after actions in the payload are completed
            "errors": {},  // form validation errors
            "return": {}, // optional return value from an executed action
            "parent": {},  // optional representation of the parent component
        }
    """
    component = UnicornView.create(
        component_id=component_request.id,
        component_name=component_request.name,
        request=request,
    )

    # Get a deepcopy of the data passed in to determine what fields are updated later
    original_data = copy.deepcopy(component_request.data)

    # Set component properties based on request data
    for (property_name, property_value) in component_request.data.items():
        set_property_from_data(component, property_name, property_value)
    component.hydrate()

    validate_all_fields = False
    is_reset_called = False
    is_refresh_called = False
    return_data = None
    partials = []

    for action in component_request.action_queue:
        if action.partial:
            partials.append(action.partial)
        else:
            partials = action.partials

        if action.action_type == "syncInput":
            sync_input.handle(component_request, component, action.payload)
        elif action.action_type == "callMethod":
            (
                component,
                _is_refresh_called,
                _is_reset_called,
                _validate_all_fields,
                return_data,
            ) = call_method.handle(component_request, component, action.payload)

            is_refresh_called = is_refresh_called | _is_refresh_called
            is_reset_called = is_reset_called | _is_reset_called
            validate_all_fields = validate_all_fields | _validate_all_fields
        else:
            raise UnicornViewError(f"Unknown action_type '{action.action_type}'")

    component.complete()

    # Re-load frontend context variables to deal with non-serializable properties
    component_request.data = orjson.loads(component.get_frontend_context_variables())

    # Get set of attributes that should be marked as `safe`
    safe_fields = []
    if hasattr(component, "Meta") and hasattr(component.Meta, "safe"):
        if isinstance(component.Meta.safe, Sequence):
            for field_name in component.Meta.safe:
                if field_name in component._attributes().keys():
                    safe_fields.append(field_name)

    # Mark safe attributes as such before rendering
    for field_name in safe_fields:
        value = getattr(component, field_name)
        if isinstance(value, str):
            setattr(component, field_name, mark_safe(value))

    # Send back all available data for reset or refresh actions
    updated_data = component_request.data

    if not is_reset_called:
        if not is_refresh_called:
            updated_data = {}

            for key, value in original_data.items():
                if value != component_request.data.get(key):
                    updated_data[key] = component_request.data.get(key)

        if validate_all_fields:
            component.validate()
        else:
            component.validate(model_names=list(updated_data.keys()))

    # Pass the current request so that it can be used inside the component template
    rendered_component = component.render(request=request)
    component.rendered(rendered_component)

    cache = caches[get_cache_alias()]

    try:
        cache.set(component.component_cache_key, get_cacheable_component(component))
    except UnicornCacheError as e:
        logger.warning(e)

    partial_doms = []

    if partials and all(partials):
        soup = BeautifulSoup(rendered_component, features="html.parser")

        for partial in partials:
            partial_found = False
            only_id = False
            only_key = False

            target = partial.get("target")

            if not target:
                target = partial.get("key")

                if target:
                    only_key = True

            if not target:
                target = partial.get("id")

                if target:
                    only_id = True

            assert target, "Partial target is required"

            if not only_id:
                for element in soup.find_all():
                    if (
                        "unicorn:key" in element.attrs
                        and element.attrs["unicorn:key"] == target
                    ):
                        partial_doms.append({"key": target, "dom": str(element)})
                        partial_found = True
                        break

            if not partial_found and not only_key:
                for element in soup.find_all():
                    if "id" in element.attrs and element.attrs["id"] == target:
                        partial_doms.append({"id": target, "dom": str(element)})
                        partial_found = True
                        break

    res = {
        "id": component_request.id,
        "data": updated_data,
        "errors": component.errors,
        "calls": component.calls,
        "checksum": generate_checksum(orjson.dumps(component_request.data)),
    }

    if partial_doms:
        res.update({"partials": partial_doms})
    else:
        hash = generate_checksum(rendered_component)

        if (
            component_request.hash == hash
            and (not return_data or not return_data.value)
            and not component.calls
        ):
            raise RenderNotModified()

        # Make sure that partials with comments or blank lines before the root element only return the root element
        soup = BeautifulSoup(rendered_component, features="html.parser")
        rendered_component = str(get_root_element(soup))

        res.update(
            {
                "dom": rendered_component,
                "hash": hash,
            }
        )

    if return_data:
        res.update(
            {
                "return": return_data.get_data(),
            }
        )

        if return_data.redirect:
            res.update(
                {
                    "redirect": return_data.redirect,
                }
            )

        if return_data.poll:
            res.update(
                {
                    "poll": return_data.poll,
                }
            )

    parent_component = component.parent

    if parent_component:
        # TODO: Should parent_component.hydrate() be called?
        parent_frontend_context_variables = loads(
            parent_component.get_frontend_context_variables()
        )
        parent_checksum = generate_checksum(dumps(parent_frontend_context_variables))

        parent = {
            "id": parent_component.component_id,
            "checksum": parent_checksum,
        }

        if not partial_doms:
            parent_dom = parent_component.render()
            component.parent_rendered(parent_dom)

            try:
                cache.set(
                    parent_component.component_cache_key,
                    get_cacheable_component(parent_component),
                )
            except UnicornCacheError as e:
                logger.warning(e)

            parent.update(
                {
                    "dom": parent_dom,
                    "data": parent_frontend_context_variables,
                    "errors": parent_component.errors,
                }
            )

        res.update({"parent": parent})

    return res