def test__create__subscription_created_on_server(self):
        token = "id"
        paths = ["tag1", "tag2", "tag3"]
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token, {})
        )

        HttpTagSubscription.create(
            self._client,
            paths,
            ManualResetTimer.null_timer,
            ManualResetTimer.null_timer,
        )

        assert self._client.all_requests.call_args_list == [
            mock.call(
                "POST",
                "/nitag/v2/subscriptions",
                params=None,
                data={"tags": paths, "updatesOnly": True},
            ),
            mock.call(
                "GET",
                "/nitag/v2/subscriptions/{id}/values/current",
                params={"id": token},
            ),
        ]
    def test__unknown_data_type_received_in_update__tag_changed_event_still_fires(self):
        token = "test subscription"
        timestamp = datetime.now(timezone.utc)
        timestamp_str = TimestampUtilities.datetime_to_str(timestamp)
        timer = mock.Mock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(
                token,
                {
                    "subscriptionUpdates": [
                        {
                            "subscriptionId": token,
                            "updates": [
                                {
                                    "value": "unknown",
                                    "type": "BLAH",
                                    "timestamp": timestamp_str,
                                    "tag": {"type": "BLAH", "path": "unknown"},
                                    "aggregates": {
                                        "min": "1.1",
                                        "max": "4.4",
                                        "count": 3,
                                        "avg": 2.88,
                                    },
                                }
                            ],
                        },
                    ],
                },
            )
        )

        def on_tag_changed(
            tag: tbase.TagData, reader: Optional[tbase.TagValueReader]
        ) -> None:
            assert tag is not None
            assert tag.path
            assert tag.path not in updates
            updates[tag.path] = (tag, reader)

        updates = (
            {}
        )  # type: Dict[str, Tuple[tbase.TagData, Optional[tbase.TagValueReader]]]
        uut = HttpTagSubscription.create(
            self._client, [], timer, ManualResetTimer.null_timer
        )
        uut.tag_changed += on_tag_changed

        timer.elapsed()

        assert 1 == len(updates)
        assert "unknown" in updates
        (tag, reader) = updates["unknown"]
        assert tbase.DataType.UNKNOWN == tag.data_type

        assert reader is None
コード例 #3
0
 def _create_subscription_internal(
     self,
     update_interval: Optional[datetime.timedelta] = None
 ) -> tbase.TagSubscription:
     update_timer = None  # type: Optional[ManualResetTimer]
     if update_interval is not None:
         update_timer = ManualResetTimer(update_interval)
     paths = set(self.paths).union(self.metadata.keys())
     return HttpTagSubscription.create(self._client,
                                       paths,
                                       update_timer,
                                       heartbeat_timer=None)
コード例 #4
0
    def test__close__subscription_deleted_from_server(self):
        token = "test subscription"
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token, {}))

        uut = HttpTagSubscription.create(self._client, [],
                                         ManualResetTimer.null_timer,
                                         ManualResetTimer.null_timer)
        uut.close()

        assert self._client.all_requests.call_args == mock.call(
            "DELETE", "/nitag/v2/subscriptions/{id}", params={"id": token})
コード例 #5
0
    def test__exit__timer_stopped_and_callback_unregistered(self):
        token = "id"
        timer = mock.MagicMock(ManualResetTimer,
                               wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token, {}))

        with HttpTagSubscription.create(self._client, [], timer,
                                        ManualResetTimer.null_timer):
            assert timer.stop.call_count == 0
            assert len(timer.elapsed) == 1

        assert timer.stop.call_count == 1
        assert len(timer.elapsed) == 0
    def test__updates_received_on_create__tag_changed_event_doesnt_see_those_updates(
        self,
    ):
        token = "test subscription"
        timestamp = datetime.now(timezone.utc)
        timestamp_str = TimestampUtilities.datetime_to_str(timestamp)
        timer = mock.Mock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        updates_list = self._one_update_of_each_type(timestamp_str)
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(
                token,
                {
                    "subscriptionUpdates": [
                        {"subscriptionId": token, "updates": updates_list}
                    ]
                },
            )
        )

        uut = HttpTagSubscription.create(
            self._client, [], timer, ManualResetTimer.null_timer
        )

        assert self._client.all_requests.call_count == 2

        def on_tag_changed(
            tag: tbase.TagData, reader: Optional[tbase.TagValueReader]
        ) -> None:
            assert False, "Should not have received any updates"

        uut.tag_changed += on_tag_changed
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(None, {})
        )

        timer.elapsed()

        assert self._client.all_requests.call_count == 3
        self._client.all_requests.assert_called_with(
            "GET", "/nitag/v2/subscriptions/{id}/values/current", params={"id": token}
        )
コード例 #7
0
    def test__update_timer_elapses__server_queried_for_updates_and_timer_reset(
            self):
        token = "test subscription"
        paths = []
        timer = mock.Mock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token, {}))

        uut = HttpTagSubscription.create(self._client, paths, timer,
                                         ManualResetTimer.null_timer)

        assert uut
        assert self._client.all_requests.call_args_list == [
            mock.call(
                "POST",
                "/nitag/v2/subscriptions",
                params=None,
                data={
                    "tags": paths,
                    "updatesOnly": True
                },
            ),
            mock.call(
                "GET",
                "/nitag/v2/subscriptions/{id}/values/current",
                params={"id": token},
            ),
        ]
        timer.start.assert_called_once_with()

        timer.elapsed()

        assert self._client.all_requests.call_count == 3
        self._client.all_requests.assert_called_with(
            "GET",
            "/nitag/v2/subscriptions/{id}/values/current",
            params={"id": token},
        )
        assert timer.start.call_count == 2
        assert len(timer.method_calls) == 2
    def test__update_fails__update_timer_elapsed__error_ignored(self):
        token = "test subscription"
        timer = mock.Mock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token, {})
        )
        updates = []

        def on_tag_changed(
            tag: tbase.TagData, reader: Optional[tbase.TagValueReader]
        ) -> None:
            updates.append((tag, reader))

        uut = HttpTagSubscription.create(
            self._client, [], timer, ManualResetTimer.null_timer
        )
        uut.tag_changed += on_tag_changed

        assert timer.start.call_count == 1
        assert self._client.all_requests.call_count == 2
        assert self._client.all_requests.call_args[0][1].endswith("/values/current")

        self._client.all_requests.configure_mock(side_effect=core.ApiException("oops"))

        timer.elapsed()
        assert timer.start.call_count == 2
        assert self._client.all_requests.call_count == 3
        assert self._client.all_requests.call_args[0][1].endswith("/values/current")

        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(None, {})
        )

        timer.elapsed()
        assert timer.start.call_count == 3
        assert self._client.all_requests.call_count == 4
        assert self._client.all_requests.call_args[0][1].endswith("/values/current")

        assert 0 == len(updates)
    def test__heartbeat_timer_elapsed__heartbeat_sent_to_server_and_timer_reset(self):
        token = "test subscription"
        timer = mock.MagicMock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token, {})
        )

        uut = HttpTagSubscription.create(
            self._client, [], ManualResetTimer.null_timer, timer
        )
        assert uut
        assert timer.start.call_count == 1

        timer.elapsed()
        assert self._client.all_requests.call_args == mock.call(
            "PUT",
            "/nitag/v2/subscriptions/{id}/heartbeat",
            params={"id": token},
            data=None,
        )
        assert timer.start.call_count == 2
    def test__heartbeat_query_errors__subscription_recreated(self):
        token1 = "test subscription"
        token2 = "second test subscription"
        paths = ["tag1", "tag2", "tag3"]
        timer = mock.MagicMock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(token1, {})
        )

        uut = HttpTagSubscription.create(
            self._client, paths, ManualResetTimer.null_timer, timer
        )

        assert uut
        assert timer.start.call_count == 1
        assert self._client.all_requests.call_count == 2
        assert self._client.all_requests.call_args_list == [
            mock.call(
                "POST",
                "/nitag/v2/subscriptions",
                params=None,
                data={"tags": paths, "updatesOnly": True},
            ),
            mock.call(
                "GET",
                "/nitag/v2/subscriptions/{id}/values/current",
                params={"id": token1},
            ),
        ]

        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(
                token2, {}, core.ApiException("Unknown subscription")
            )
        )

        timer.elapsed()

        assert timer.start.call_count == 2
        assert self._client.all_requests.call_count == 5
        assert self._client.all_requests.call_args_list[-3:] == [
            mock.call(
                "PUT",
                "/nitag/v2/subscriptions/{id}/heartbeat",
                params={"id": token1},
                data=None,
            ),
            mock.call(
                "POST",
                "/nitag/v2/subscriptions",
                params=None,
                data={"tags": paths, "updatesOnly": True},
            ),
            mock.call(
                "GET",
                "/nitag/v2/subscriptions/{id}/values/current",
                params={"id": token2},
            ),
        ]

        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(None, None)
        )

        timer.elapsed()

        assert timer.start.call_count == 3
        assert self._client.all_requests.call_count == 6
        assert self._client.all_requests.call_args_list[-1:] == [
            mock.call(
                "PUT",
                "/nitag/v2/subscriptions/{id}/heartbeat",
                params={"id": token2},
                data=None,
            ),
        ]
    def test__invalid_subscription_updates_received__those_updates_skipped(self):
        token = "test subscription"
        timestamp = datetime.now(timezone.utc)
        timestamp_str = TimestampUtilities.datetime_to_str(timestamp)
        timer = mock.Mock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        good_update = self._one_update_of_each_type(timestamp_str)[0]
        updates_list = [
            None,
            {},
            {"value": "invalid"},
            {"value": "invalid", "type": "STRING"},
            {
                "value": "invalid",
                "type": "STRING",
                "tag": {"type": "STRING", "path": "invalid"},
            },
            {
                "value": "invalid",
                "type": "STRING",
                "timestamp": None,
                "tag": {"type": "STRING", "path": "invalid"},
            },
            {"value": "invalid", "type": "STRING", "timestamp": timestamp_str},
            {
                "value": "invalid",
                "type": "STRING",
                "timestamp": timestamp_str,
                "tag": {"type": "STRING", "path": ""},
            },
            {
                "value": "invalid",
                "type": "BOOLEAN",
                "timestamp": timestamp_str,
                "tag": {"type": "BOOLEAN", "path": "bool"},
            },
            good_update,
        ]
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(
                token,
                {
                    "subscriptionUpdates": [
                        None,
                        {},
                        {"updates": []},
                        {"subscriptionId": token, "updates": updates_list},
                    ],
                },
            )
        )

        def on_tag_changed(
            tag: tbase.TagData, value: Optional[tbase.TagValueReader]
        ) -> None:
            assert tag is not None
            assert tag.path
            assert tag.path not in updates
            updates[tag.path] = (tag, value)

        updates = (
            {}
        )  # type: Dict[str, Tuple[tbase.TagData, Optional[tbase.TagValueReader]]]
        uut = HttpTagSubscription.create(
            self._client, [], timer, ManualResetTimer.null_timer
        )
        uut.tag_changed += on_tag_changed

        timer.elapsed()

        assert 2 == len(updates)
        assert "bool" in updates
        (tag, reader) = updates["bool"]
        assert tbase.DataType.BOOLEAN == tag.data_type
        with pytest.raises(core.ApiException):
            reader.read()

        assert "double" in updates
        (tag, reader) = updates["double"]
        assert tbase.DataType.DOUBLE == tag.data_type
        value = reader.read(include_timestamp=True, include_aggregates=True)
        assert 3.14 == value.value
        assert timestamp == value.timestamp
        assert value.count is not None
        assert 1.1 == value.min
        assert 4.4 == value.max
        assert 3 == value.count
        assert 2.88 == value.mean
    def test__tags_updates_received_from_server__tag_changed_event_fired_with_each_update(
        self,
    ):
        token = "test subscription"
        timestamp = datetime.now(timezone.utc)
        timestamp_str = TimestampUtilities.datetime_to_str(timestamp)
        timer = mock.Mock(ManualResetTimer, wraps=ManualResetTimer.null_timer)
        type(timer).elapsed = events.events._EventSlot("elapsed")
        updates_list = self._one_update_of_each_type(timestamp_str)
        self._client.all_requests.configure_mock(
            side_effect=self._get_mock_request(
                token,
                {
                    "subscriptionUpdates": [
                        {"subscriptionId": token, "updates": updates_list}
                    ]
                },
            )
        )

        updates = (
            {}
        )  # type: Dict[str, Tuple[tbase.TagData, Optional[tbase.TagValueReader]]]
        uut = HttpTagSubscription.create(
            self._client, [], timer, ManualResetTimer.null_timer
        )

        def on_tag_changed(
            tag: tbase.TagData, reader: Optional[tbase.TagValueReader]
        ) -> None:
            assert tag is not None
            assert tag.path
            assert tag.path not in updates
            updates[tag.path] = (tag, reader)

        uut.tag_changed += on_tag_changed

        timer.elapsed()

        value_converters = {
            "DOUBLE": float,
            "INT": int,
            "STRING": str,
            "BOOLEAN": {"True": True, "False": False}.get,
            "U_INT64": int,
            "DATE_TIME": TimestampUtilities.str_to_datetime,
        }

        def check_args(
            data: Dict[str, Any],
            tag: tbase.TagData,
            reader: Optional[tbase.TagValueReader],
        ) -> None:
            assert tbase.DataType.from_api_name(data["type"]) == tag.data_type
            convert_value = value_converters[data["type"]]
            assert reader is not None
            value = reader.read(include_timestamp=True, include_aggregates=True)
            assert value is not None
            assert convert_value(data["value"]) == value.value
            assert timestamp == value.timestamp
            if "aggregates" not in data:
                assert value.count is None
            else:
                assert value.count is not None
                assert data["aggregates"]["count"] == value.count
                if "avg" in data["aggregates"]:
                    assert data["aggregates"]["avg"] == value.mean
                    assert convert_value(data["aggregates"]["min"]) == value.min
                    assert convert_value(data["aggregates"]["max"]) == value.max

        assert 6 == len(updates)
        for u in updates_list:
            path = u["tag"]["path"]
            assert path in updates
            check_args(u, *updates[path])