Files
rasahq--rasa/tests/core/test_tracker_stores.py
wehub-resource-sync dc6079821b
CI Github Actions / Run Tests (push) Waiting to run
Automatic PR Merger / mergepal (push) Waiting to run
Semgrep / Semgrep Workflow Security Scan (push) Waiting to run
Docs Tests / Check for file changes (push) Has been cancelled
Docs Tests / Test Documentation (push) Has been cancelled
Docs Tests / Documentation Linting Checks (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.9) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.9) (push) Has been cancelled
Continuous Integration / Check for file changes (push) Has been cancelled
Continuous Integration / Wait for docs tests (push) Has been cancelled
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Check for changelog (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Upload coverage reports to codeclimate (push) Has been cancelled
Continuous Integration / Run Non-Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Run Broker Integration Tests (push) Has been cancelled
Continuous Integration / Run Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Build Docker base images and setup environment (push) Has been cancelled
Continuous Integration / Build Docker (default) (push) Has been cancelled
Continuous Integration / Build Docker (full) (push) Has been cancelled
Continuous Integration / Build Docker (mitie-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-de) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-it) (push) Has been cancelled
Continuous Integration / Deploy to PyPI (push) Has been cancelled
Continuous Integration / Notify Slack & Publish Release Notes (push) Has been cancelled
Publish Documentation / Evaluate release tag (push) Has been cancelled
Publish Documentation / Prebuild Docs (push) Has been cancelled
Publish Documentation / Preview Docs (push) Has been cancelled
Publish Documentation / Check for file changes (push) Has been cancelled
Publish Documentation / Publish Docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:47 +08:00

1345 lines
43 KiB
Python

import logging
import warnings
from collections import deque
from contextlib import contextmanager
from pathlib import Path
import fakeredis
import pytest
import sqlalchemy
import uuid
from _pytest.capture import CaptureFixture
from _pytest.logging import LogCaptureFixture
from _pytest.monkeypatch import MonkeyPatch
from moto import mock_dynamodb
from pymongo.errors import OperationFailure
from rasa.core.agent import Agent
from rasa.nlu.tokenizers.whitespace_tokenizer import WhitespaceTokenizer
from rasa.shared.constants import DEFAULT_SENDER_ID
from sqlalchemy.dialects.postgresql.base import PGDialect
from sqlalchemy.dialects.sqlite.base import SQLiteDialect
from sqlalchemy.dialects.oracle.base import OracleDialect
from sqlalchemy.engine.url import URL
from typing import Any, Tuple, Text, Type, Dict, List, Union, Optional, ContextManager
from unittest.mock import MagicMock, Mock
import rasa.core.tracker_store
from rasa.shared.core.constants import (
ACTION_LISTEN_NAME,
ACTION_RESTART_NAME,
ACTION_SESSION_START_NAME,
)
from rasa.core.constants import POSTGRESQL_SCHEMA
from rasa.shared.core.domain import Domain
from rasa.shared.core.events import (
SlotSet,
ActionExecuted,
Restarted,
UserUttered,
SessionStarted,
BotUttered,
Event,
)
from rasa.shared.exceptions import ConnectionException, RasaException
from rasa.core.tracker_store import (
TrackerStore,
InMemoryTrackerStore,
RedisTrackerStore,
DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX,
SQLTrackerStore,
DynamoTrackerStore,
FailSafeTrackerStore,
AwaitableTrackerStore,
)
from rasa.shared.core.trackers import DialogueStateTracker, TrackerEventDiffEngine
from rasa.shared.nlu.training_data.message import Message
from rasa.utils.endpoints import EndpointConfig, read_endpoint_config
from tests.conftest import AsyncMock
from tests.core.conftest import MockedMongoTrackerStore
test_domain = Domain.load("data/test_domains/default.yml")
async def get_or_create_tracker_store(store: TrackerStore) -> None:
slot_key = "location"
slot_val = "Easter Island"
tracker = await store.get_or_create_tracker(DEFAULT_SENDER_ID)
ev = SlotSet(slot_key, slot_val)
tracker.update(ev)
assert tracker.get_slot(slot_key) == slot_val
await store.save(tracker)
again = await store.get_or_create_tracker(DEFAULT_SENDER_ID)
assert again.get_slot(slot_key) == slot_val
def test_get_or_create():
get_or_create_tracker_store(InMemoryTrackerStore(test_domain))
# noinspection PyPep8Naming
@mock_dynamodb
def test_dynamo_get_or_create():
get_or_create_tracker_store(DynamoTrackerStore(test_domain))
@mock_dynamodb
async def test_dynamo_tracker_floats():
conversation_id = uuid.uuid4().hex
tracker_store = DynamoTrackerStore(test_domain)
tracker = await tracker_store.get_or_create_tracker(
conversation_id, append_action_listen=False
)
# save `slot` event with known `float`-type timestamp
timestamp = 13423.23434623
tracker.update(SlotSet("key", "val", timestamp=timestamp))
await tracker_store.save(tracker)
# retrieve tracker and the event timestamp is retrieved as a `float`
tracker = await tracker_store.get_or_create_tracker(conversation_id)
retrieved_timestamp = tracker.events[0].timestamp
assert isinstance(retrieved_timestamp, float)
assert retrieved_timestamp == timestamp
async def test_restart_after_retrieval_from_tracker_store(domain: Domain):
store = InMemoryTrackerStore(domain)
tr = await store.get_or_create_tracker("myuser")
synth = [ActionExecuted("action_listen") for _ in range(4)]
for e in synth:
tr.update(e)
tr.update(Restarted())
latest_restart = tr.idx_after_latest_restart()
await store.save(tr)
tr2 = await store.retrieve("myuser")
latest_restart_after_loading = tr2.idx_after_latest_restart()
assert latest_restart == latest_restart_after_loading
async def test_tracker_store_remembers_max_history(domain: Domain):
store = InMemoryTrackerStore(domain)
tr = await store.get_or_create_tracker("myuser", max_event_history=42)
tr.update(Restarted())
await store.save(tr)
tr2 = await store.retrieve("myuser")
assert tr._max_event_history == tr2._max_event_history == 42
def test_tracker_store_endpoint_config_loading(endpoints_path: Text):
cfg = read_endpoint_config(endpoints_path, "tracker_store")
assert cfg == EndpointConfig.from_dict(
{
"type": "redis",
"url": "localhost",
"port": 6379,
"db": 0,
"username": "username",
"password": "password",
"timeout": 30000,
"use_ssl": True,
"ssl_keyfile": "keyfile.key",
"ssl_certfile": "certfile.crt",
"ssl_ca_certs": "my-bundle.ca-bundle",
}
)
def test_create_tracker_store_from_endpoint_config(
domain: Domain, endpoints_path: Text
):
store = read_endpoint_config(endpoints_path, "tracker_store")
tracker_store = RedisTrackerStore(
domain=domain,
host="localhost",
port=6379,
db=0,
username="username",
password="password",
record_exp=3000,
use_ssl=True,
ssl_keyfile="keyfile.key",
ssl_certfile="certfile.crt",
ssl_ca_certs="my-bundle.ca-bundle",
)
assert isinstance(tracker_store, type(TrackerStore.create(store, domain)))
def test_redis_tracker_store_invalid_key_prefix(domain: Domain):
test_invalid_key_prefix = "$$ &!"
tracker_store = RedisTrackerStore(
domain=domain,
host="localhost",
port=6379,
db=0,
password="password",
key_prefix=test_invalid_key_prefix,
record_exp=3000,
)
assert tracker_store._get_key_prefix() == DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
def test_redis_tracker_store_valid_key_prefix(domain: Domain):
test_valid_key_prefix = "spanish"
tracker_store = RedisTrackerStore(
domain=domain,
host="localhost",
port=6379,
db=0,
password="password",
key_prefix=test_valid_key_prefix,
record_exp=3000,
)
assert (
tracker_store._get_key_prefix()
== f"{test_valid_key_prefix}:{DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX}"
)
def test_exception_tracker_store_from_endpoint_config(
domain: Domain, monkeypatch: MonkeyPatch, endpoints_path: Text
):
"""Check if tracker store properly handles exceptions.
If we can not create a tracker store by instantiating the
expected type (e.g. due to an exception) we should fallback to
the default `InMemoryTrackerStore`."""
store = read_endpoint_config(endpoints_path, "tracker_store")
mock = Mock(side_effect=Exception("test exception"))
monkeypatch.setattr(rasa.core.tracker_store, "RedisTrackerStore", mock)
with pytest.raises(Exception) as e:
TrackerStore.create(store, domain)
assert "test exception" in str(e.value)
def test_raise_connection_exception_redis_tracker_store_creation(
domain: Domain, monkeypatch: MonkeyPatch, endpoints_path: Text
):
store = read_endpoint_config(endpoints_path, "tracker_store")
monkeypatch.setattr(
rasa.core.tracker_store,
"RedisTrackerStore",
Mock(side_effect=ConnectionError()),
)
with pytest.raises(ConnectionException):
TrackerStore.create(store, domain)
def test_mongo_tracker_store_raise_exception(domain: Domain, monkeypatch: MonkeyPatch):
monkeypatch.setattr(
rasa.core.tracker_store,
"MongoTrackerStore",
Mock(
side_effect=OperationFailure("not authorized on logs to execute command.")
),
)
with pytest.raises(ConnectionException) as error:
TrackerStore.create(
EndpointConfig(username="username", password="password", type="mongod"),
domain,
)
assert "not authorized on logs to execute command." in str(error.value)
class HostExampleTrackerStore(RedisTrackerStore):
pass
class NonAsyncTrackerStore(TrackerStore):
def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
pass
def save(self, tracker: DialogueStateTracker) -> None:
pass
def test_tracker_store_with_host_argument_from_string(domain: Domain):
endpoints_path = "data/test_endpoints/custom_tracker_endpoints.yml"
store_config = read_endpoint_config(endpoints_path, "tracker_store")
store_config.type = "tests.core.test_tracker_stores.HostExampleTrackerStore"
with warnings.catch_warnings(record=True) as record:
warnings.simplefilter("error")
tracker_store = TrackerStore.create(store_config, domain)
assert len(record) == 0
assert isinstance(tracker_store, HostExampleTrackerStore)
def test_tracker_store_from_invalid_module(domain: Domain):
endpoints_path = "data/test_endpoints/custom_tracker_endpoints.yml"
store_config = read_endpoint_config(endpoints_path, "tracker_store")
store_config.type = "a.module.which.cannot.be.found"
with pytest.warns(UserWarning):
tracker_store = TrackerStore.create(store_config, domain)
assert isinstance(tracker_store, InMemoryTrackerStore)
def test_tracker_store_from_invalid_string(domain: Domain):
endpoints_path = "data/test_endpoints/custom_tracker_endpoints.yml"
store_config = read_endpoint_config(endpoints_path, "tracker_store")
store_config.type = "any string"
with pytest.warns(UserWarning):
tracker_store = TrackerStore.create(store_config, domain)
assert isinstance(tracker_store, InMemoryTrackerStore)
async def _tracker_store_and_tracker_with_slot_set() -> Tuple[
InMemoryTrackerStore, DialogueStateTracker
]:
# returns an InMemoryTrackerStore containing a tracker with a slot set
slot_key = "cuisine"
slot_val = "French"
store = InMemoryTrackerStore(test_domain)
tracker = await store.get_or_create_tracker(DEFAULT_SENDER_ID)
ev = SlotSet(slot_key, slot_val)
tracker.update(ev)
return store, tracker
async def test_tracker_serialisation():
store, tracker = await _tracker_store_and_tracker_with_slot_set()
serialised = store.serialise_tracker(tracker)
assert tracker == store.deserialise_tracker(DEFAULT_SENDER_ID, serialised)
@pytest.mark.parametrize(
"full_url",
[
"postgresql://localhost",
"postgresql://localhost:5432",
"postgresql://user:secret@localhost",
"sqlite:///",
],
)
def test_get_db_url_with_fully_specified_url(full_url: Text):
assert SQLTrackerStore.get_db_url(host=full_url) == full_url
def test_get_db_url_with_port_in_host():
host = "localhost:1234"
dialect = "postgresql"
db = "mydb"
expected = f"{dialect}://{host}/{db}"
assert (
str(SQLTrackerStore.get_db_url(dialect=dialect, host=host, db=db)) == expected
)
def test_db_get_url_with_sqlite():
expected = "sqlite:///rasa.db"
assert str(SQLTrackerStore.get_db_url(dialect="sqlite", db="rasa.db")) == expected
def test_get_db_url_with_correct_host():
expected = "postgresql://localhost:5005/mydb"
assert (
str(
SQLTrackerStore.get_db_url(
dialect="postgresql", host="localhost", port=5005, db="mydb"
)
)
== expected
)
def test_get_db_url_with_query():
expected = "postgresql://localhost:5005/mydb?driver=my-driver"
assert (
str(
SQLTrackerStore.get_db_url(
dialect="postgresql",
host="localhost",
port=5005,
db="mydb",
query={"driver": "my-driver"},
)
)
== expected
)
def test_sql_tracker_store_logs_do_not_show_password(caplog: LogCaptureFixture):
dialect = "postgresql"
host = "localhost"
port = 9901
db = "some-database"
username = "db-user"
password = "some-password"
with caplog.at_level(logging.DEBUG):
_ = SQLTrackerStore(None, dialect, host, port, db, username, password)
# the URL in the logs does not contain the password
assert password not in caplog.text
# instead the password is displayed as '***'
assert f"postgresql://{username}:***@{host}:{port}/{db}" in caplog.text
def test_db_url_with_query_from_endpoint_config(tmp_path: Path):
endpoint_config = """
tracker_store:
dialect: postgresql
url: localhost
port: 5123
username: user
password: pw
login_db: login-db
query:
driver: my-driver
another: query
"""
f = tmp_path / "tmp_config_file.yml"
f.write_text(endpoint_config)
store_config = read_endpoint_config(str(f), "tracker_store")
url = SQLTrackerStore.get_db_url(**store_config.kwargs)
import itertools
# order of query dictionary in yaml is random, test against both permutations
connection_url = "postgresql://user:pw@:5123/login-db?"
assert any(
str(url) == connection_url + "&".join(permutation)
for permutation in (
itertools.permutations(("another=query", "driver=my-driver"))
)
)
async def test_fail_safe_tracker_store_if_no_errors():
mocked_tracker_store = Mock()
tracker_store = FailSafeTrackerStore(mocked_tracker_store, None)
# test save
mocked_tracker_store.save = AsyncMock()
await tracker_store.save(None)
mocked_tracker_store.save.assert_called_once()
# test retrieve
expected = [1]
mocked_tracker_store.retrieve = AsyncMock(return_value=expected)
sender_id = "10"
assert await tracker_store.retrieve(sender_id) == expected
mocked_tracker_store.retrieve.assert_called_once_with(sender_id)
# test keys
expected = ["sender 1", "sender 2"]
mocked_tracker_store.keys = AsyncMock(return_value=expected)
assert await tracker_store.keys() == expected
mocked_tracker_store.keys.assert_called_once()
async def test_fail_safe_tracker_store_with_save_error():
mocked_tracker_store = Mock()
mocked_tracker_store.save = Mock(side_effect=Exception())
fallback_tracker_store = Mock()
fallback_tracker_store.save = AsyncMock()
on_error_callback = Mock()
tracker_store = FailSafeTrackerStore(
mocked_tracker_store, on_error_callback, fallback_tracker_store
)
await tracker_store.save(None)
fallback_tracker_store.save.assert_called_once()
on_error_callback.assert_called_once()
async def test_fail_safe_tracker_store_with_keys_error():
mocked_tracker_store = Mock()
mocked_tracker_store.keys = Mock(side_effect=Exception())
on_error_callback = Mock()
tracker_store = FailSafeTrackerStore(mocked_tracker_store, on_error_callback)
assert await tracker_store.keys() == []
on_error_callback.assert_called_once()
async def test_fail_safe_tracker_store_with_retrieve_error():
mocked_tracker_store = Mock()
mocked_tracker_store.retrieve = Mock(side_effect=Exception())
fallback_tracker_store = Mock()
on_error_callback = Mock()
tracker_store = FailSafeTrackerStore(
mocked_tracker_store, on_error_callback, fallback_tracker_store
)
assert await tracker_store.retrieve("sender_id") is None
on_error_callback.assert_called_once()
def test_set_fail_safe_tracker_store_domain(domain: Domain):
tracker_store = InMemoryTrackerStore(domain)
fallback_tracker_store = InMemoryTrackerStore(None)
failsafe_store = FailSafeTrackerStore(tracker_store, None, fallback_tracker_store)
failsafe_store.domain = domain
assert failsafe_store.domain is domain
assert tracker_store.domain is failsafe_store.domain
assert fallback_tracker_store.domain is failsafe_store.domain
async def create_tracker_with_partially_saved_events(
tracker_store: TrackerStore,
) -> Tuple[List[Event], DialogueStateTracker]:
# creates a tracker with two events and saved it to the tracker store
# following that, it adds three more events that are not saved to the tracker store
sender_id = uuid.uuid4().hex
# create tracker with two events and save it
events = [UserUttered("hello"), BotUttered("what")]
tracker = DialogueStateTracker.from_events(sender_id, events)
await tracker_store.save(tracker)
# add more events to the tracker, do not yet save it
events = [ActionExecuted(ACTION_LISTEN_NAME), UserUttered("123"), BotUttered("yes")]
for event in events:
tracker.update(event)
return events, tracker
async def _saved_tracker_with_multiple_session_starts(
tracker_store: TrackerStore, sender_id: Text
) -> DialogueStateTracker:
tracker = DialogueStateTracker.from_events(
sender_id,
[
ActionExecuted(ACTION_SESSION_START_NAME),
SessionStarted(),
UserUttered("hi"),
ActionExecuted(ACTION_SESSION_START_NAME),
SessionStarted(),
],
)
await tracker_store.save(tracker)
return await tracker_store.retrieve(sender_id)
async def test_mongo_additional_events(domain: Domain):
tracker_store = MockedMongoTrackerStore(domain)
events, tracker = await create_tracker_with_partially_saved_events(tracker_store)
# make sure only new events are returned
# noinspection PyProtectedMember
assert list(tracker_store._additional_events(tracker)) == events
async def test_mongo_additional_events_with_session_start(domain: Domain):
sender = "test_mongo_additional_events_with_session_start"
tracker_store = MockedMongoTrackerStore(domain)
tracker = await _saved_tracker_with_multiple_session_starts(tracker_store, sender)
tracker.update(UserUttered("hi2"))
# noinspection PyProtectedMember
additional_events = list(tracker_store._additional_events(tracker))
assert len(additional_events) == 1
assert isinstance(additional_events[0], UserUttered)
# we cannot parametrise over this and the previous test due to the different ways of
# calling _additional_events()
async def test_sql_additional_events(domain: Domain):
tracker_store = SQLTrackerStore(domain)
additional_events, tracker = await create_tracker_with_partially_saved_events(
tracker_store
)
# make sure only new events are returned
with tracker_store.session_scope() as session:
# noinspection PyProtectedMember
assert (
list(tracker_store._additional_events(session, tracker))
== additional_events
)
async def test_sql_additional_events_with_session_start(domain: Domain):
sender = "test_sql_additional_events_with_session_start"
tracker_store = SQLTrackerStore(domain)
tracker = await _saved_tracker_with_multiple_session_starts(tracker_store, sender)
tracker.update(UserUttered("hi2"), domain)
# make sure only new events are returned
with tracker_store.session_scope() as session:
# noinspection PyProtectedMember
additional_events = list(tracker_store._additional_events(session, tracker))
assert len(additional_events) == 1
assert isinstance(additional_events[0], UserUttered)
async def test_tracker_store_retrieve_ordered_by_id(
domain: Domain,
):
tracker_store_kwargs = {"host": "sqlite:///"}
tracker_store = SQLTrackerStore(domain, **tracker_store_kwargs)
events = [
SessionStarted(timestamp=1),
UserUttered("Hola", {"name": "greet"}, timestamp=2),
BotUttered("Hi", timestamp=2),
UserUttered("How are you?", {"name": "greet"}, timestamp=2),
BotUttered("I am good, whats up", timestamp=2),
UserUttered("Ciao", {"name": "greet"}, timestamp=2),
BotUttered("Bye", timestamp=2),
]
sender_id = "test_sql_tracker_store_events_order"
tracker = DialogueStateTracker.from_events(sender_id, events)
await tracker_store.save(tracker)
# Save other tracker to ensure that we don't run into problems with other senders
other_tracker = DialogueStateTracker.from_events("other-sender", [SessionStarted()])
await tracker_store.save(other_tracker)
# Retrieve tracker with events since latest SessionStarted
tracker = await tracker_store.retrieve(sender_id)
assert len(tracker.events) == 7
# assert the order of events is same as the order in which they were added
assert all((event == tracker.events[i] for i, event in enumerate(events)))
@pytest.mark.parametrize(
"tracker_store_type,tracker_store_kwargs",
[(MockedMongoTrackerStore, {}), (SQLTrackerStore, {"host": "sqlite:///"})],
)
async def test_tracker_store_retrieve_with_session_started_events(
tracker_store_type: Type[TrackerStore],
tracker_store_kwargs: Dict,
domain: Domain,
):
tracker_store = tracker_store_type(domain, **tracker_store_kwargs)
events = [
UserUttered("Hola", {"name": "greet"}, timestamp=1),
BotUttered("Hi", timestamp=2),
SessionStarted(timestamp=3),
UserUttered("Ciao", {"name": "greet"}, timestamp=4),
]
sender_id = "test_sql_tracker_store_with_session_events"
tracker = DialogueStateTracker.from_events(sender_id, events)
await tracker_store.save(tracker)
# Save other tracker to ensure that we don't run into problems with other senders
other_tracker = DialogueStateTracker.from_events("other-sender", [SessionStarted()])
await tracker_store.save(other_tracker)
# Retrieve tracker with events since latest SessionStarted
tracker = await tracker_store.retrieve(sender_id)
assert len(tracker.events) == 2
assert all((event == tracker.events[i] for i, event in enumerate(events[2:])))
@pytest.mark.parametrize(
"tracker_store_type,tracker_store_kwargs",
[(MockedMongoTrackerStore, {}), (SQLTrackerStore, {"host": "sqlite:///"})],
)
async def test_tracker_store_retrieve_without_session_started_events(
tracker_store_type: Type[TrackerStore],
tracker_store_kwargs: Dict,
domain,
):
tracker_store = tracker_store_type(domain, **tracker_store_kwargs)
# Create tracker with a SessionStarted event
events = [
UserUttered("Hola", {"name": "greet"}),
BotUttered("Hi"),
UserUttered("Ciao", {"name": "greet"}),
BotUttered("Hi2"),
]
sender_id = "test_sql_tracker_store_retrieve_without_session_started_events"
tracker = DialogueStateTracker.from_events(sender_id, events)
await tracker_store.save(tracker)
# Save other tracker to ensure that we don't run into problems with other senders
other_tracker = DialogueStateTracker.from_events("other-sender", [SessionStarted()])
await tracker_store.save(other_tracker)
tracker = await tracker_store.retrieve(sender_id)
assert len(tracker.events) == 4
assert all(event == tracker.events[i] for i, event in enumerate(events))
@pytest.mark.parametrize(
"tracker_store_type,tracker_store_kwargs",
[
(MockedMongoTrackerStore, {}),
(SQLTrackerStore, {"host": "sqlite:///"}),
(InMemoryTrackerStore, {}),
],
)
async def test_tracker_store_retrieve_with_events_from_previous_sessions(
tracker_store_type: Type[TrackerStore], tracker_store_kwargs: Dict
):
tracker_store = tracker_store_type(Domain.empty(), **tracker_store_kwargs)
conversation_id = uuid.uuid4().hex
tracker = DialogueStateTracker.from_events(
conversation_id,
[
ActionExecuted(ACTION_SESSION_START_NAME),
SessionStarted(),
UserUttered("hi"),
ActionExecuted(ACTION_SESSION_START_NAME),
SessionStarted(),
],
)
await tracker_store.save(tracker)
actual = await tracker_store.retrieve_full_tracker(conversation_id)
assert len(actual.events) == len(tracker.events)
def test_session_scope_error(
monkeypatch: MonkeyPatch, capsys: CaptureFixture, domain: Domain
):
tracker_store = SQLTrackerStore(domain)
tracker_store.sessionmaker = Mock()
requested_schema = uuid.uuid4().hex
# `ensure_schema_exists()` raises `ValueError`
mocked_ensure_schema_exists = Mock(side_effect=ValueError(requested_schema))
monkeypatch.setattr(
rasa.core.tracker_store, "ensure_schema_exists", mocked_ensure_schema_exists
)
# `SystemExit` is triggered by failing `ensure_schema_exists()`
with pytest.raises(SystemExit):
with tracker_store.session_scope() as _:
pass
# error message is printed
assert (
f"Requested PostgreSQL schema '{requested_schema}' was not found in the "
f"database." in capsys.readouterr()[0]
)
@pytest.mark.parametrize(
"url,is_postgres_url",
[
(f"{PGDialect.name}://admin:pw@localhost:5432/rasa", True),
(f"{SQLiteDialect.name}:///", False),
(URL(PGDialect.name), True),
(URL(SQLiteDialect.name), False),
],
)
def test_is_postgres_url(url: Union[Text, URL], is_postgres_url: bool):
assert rasa.core.tracker_store.is_postgresql_url(url) == is_postgres_url
def set_or_delete_postgresql_schema_env_var(
monkeypatch: MonkeyPatch, value: Optional[Text]
) -> None:
"""Set `POSTGRESQL_SCHEMA` environment variable using `MonkeyPatch`.
Args:
monkeypatch: Instance of `MonkeyPatch` to use for patching.
value: Value of the `POSTGRESQL_SCHEMA` environment variable to set.
"""
if value is None:
monkeypatch.delenv(POSTGRESQL_SCHEMA, raising=False)
else:
monkeypatch.setenv(POSTGRESQL_SCHEMA, value)
@pytest.mark.parametrize(
"url,schema_env,kwargs",
[
# postgres without schema
(
f"{PGDialect.name}://admin:pw@localhost:5432/rasa",
None,
{
"pool_size": rasa.core.tracker_store.POSTGRESQL_DEFAULT_POOL_SIZE,
"max_overflow": rasa.core.tracker_store.POSTGRESQL_DEFAULT_MAX_OVERFLOW,
},
),
# postgres with schema
(
f"{PGDialect.name}://admin:pw@localhost:5432/rasa",
"schema1",
{
"connect_args": {"options": "-csearch_path=schema1"},
"pool_size": rasa.core.tracker_store.POSTGRESQL_DEFAULT_POOL_SIZE,
"max_overflow": rasa.core.tracker_store.POSTGRESQL_DEFAULT_MAX_OVERFLOW,
},
),
# oracle without schema
(f"{OracleDialect.name}://admin:pw@localhost:5432/rasa", None, {}),
# oracle with schema
(f"{OracleDialect.name}://admin:pw@localhost:5432/rasa", "schema1", {}),
# sqlite
(f"{SQLiteDialect.name}:///", None, {}),
],
)
def test_create_engine_kwargs(
monkeypatch: MonkeyPatch,
url: Union[Text, URL],
schema_env: Optional[Text],
kwargs: Dict[Text, Dict[Text, Union[Text, int]]],
):
set_or_delete_postgresql_schema_env_var(monkeypatch, schema_env)
assert rasa.core.tracker_store.create_engine_kwargs(url) == kwargs
@contextmanager
def does_not_raise():
"""Contextmanager to be used when an expression is not expected to raise an
exception.
This contextmanager can be used in parametrized tests, where some input objects
are expected to raise and others are not.
Example:
@pytest.mark.parametrize(
"a,b,raises_context",
[
# 5/6 is a legal divison
(5, 6, does_not_raise()),
# 5/0 raises a `ZeroDivisionError`
(5, 0, pytest.raises(ZeroDivisionError)),
],
)
def test_divide(
a: int, b: int, raises_context: ContextManager,
):
with raises_context:
_ = a / b
"""
yield
@pytest.mark.parametrize(
"is_postgres,schema_env,schema_exists,raises_context",
[
(True, "schema1", True, does_not_raise()),
(True, "schema1", False, pytest.raises(ValueError)),
(False, "schema1", False, does_not_raise()),
(True, None, False, does_not_raise()),
(False, None, False, does_not_raise()),
],
)
def test_ensure_schema_exists(
monkeypatch: MonkeyPatch,
is_postgres: bool,
schema_env: Optional[Text],
schema_exists: bool,
raises_context: ContextManager,
):
set_or_delete_postgresql_schema_env_var(monkeypatch, schema_env)
monkeypatch.setattr(
rasa.core.tracker_store, "is_postgresql_url", lambda _: is_postgres
)
monkeypatch.setattr(sqlalchemy, "exists", Mock())
# mock the `session.query().scalar()` query which returns whether the schema
# exists in the db
scalar = Mock(return_value=schema_exists)
query = Mock(scalar=scalar)
session = Mock()
session.query = Mock(return_value=query)
with raises_context:
rasa.core.tracker_store.ensure_schema_exists(session)
def test_current_state_without_events(domain: Domain):
tracker_store = MockedMongoTrackerStore(domain)
# insert some events
events = [
UserUttered("Hola", {"name": "greet"}),
BotUttered("Hi"),
UserUttered("Ciao", {"name": "greet"}),
BotUttered("Hi2"),
]
sender_id = "test_mongo_tracker_store_current_state_without_events"
tracker = DialogueStateTracker.from_events(sender_id, events)
# get current state without events
# noinspection PyProtectedMember
state = tracker_store._current_tracker_state_without_events(tracker)
# `events` key should not be in there
assert state and "events" not in state
def test_login_db_with_no_postgresql(tmp_path: Path):
with pytest.warns(UserWarning):
SQLTrackerStore(db=str(tmp_path / "rasa.db"), login_db=str(tmp_path / "other"))
@pytest.mark.parametrize(
"config",
[
{
"type": "mongod",
"url": "mongodb://0.0.0.0:42/?serverSelectionTimeoutMS=5000",
},
{"type": "dynamo"},
],
)
def test_tracker_store_connection_error(config: Dict, domain: Domain):
store = EndpointConfig.from_dict(config)
with pytest.raises(ConnectionException):
TrackerStore.create(store, domain)
async def prepare_token_serialisation(
tracker_store: TrackerStore, response_selector_agent: Agent, sender_id: Text
):
text = "Good morning"
tokenizer = WhitespaceTokenizer(WhitespaceTokenizer.get_default_config())
tokens = tokenizer.tokenize(Message(data={"text": text}), "text")
indices = [[t.start, t.end] for t in tokens]
tracker = await tracker_store.get_or_create_tracker(sender_id=sender_id)
parse_data = await response_selector_agent.parse_message(text)
event = UserUttered(
"Good morning",
parse_data.get("intent"),
parse_data.get("entities", []),
parse_data,
)
tracker.update(event)
await tracker_store.save(tracker)
retrieved_tracker = await tracker_store.retrieve(sender_id=sender_id)
event = retrieved_tracker.get_last_event_for(event_type=UserUttered)
event_tokens = event.as_dict().get("parse_data").get("text_tokens")
assert event_tokens == indices
def test_inmemory_tracker_store_with_token_serialisation(
domain: Domain, default_agent: Agent
):
tracker_store = InMemoryTrackerStore(domain)
prepare_token_serialisation(tracker_store, default_agent, "inmemory")
def test_mongo_tracker_store_with_token_serialisation(
domain: Domain, response_selector_agent: Agent
):
tracker_store = MockedMongoTrackerStore(domain)
prepare_token_serialisation(tracker_store, response_selector_agent, "mongo")
def test_sql_tracker_store_with_token_serialisation(
domain: Domain, response_selector_agent: Agent
):
tracker_store = SQLTrackerStore(domain, **{"host": "sqlite:///"})
prepare_token_serialisation(tracker_store, response_selector_agent, "sql")
def test_sql_tracker_store_creation_with_invalid_port(domain: Domain):
with pytest.raises(RasaException) as error:
TrackerStore.create(
EndpointConfig(port="$DB_PORT", type="sql"),
domain,
)
assert "port '$DB_PORT' cannot be cast to integer." in str(error.value)
def test_create_non_async_tracker_store(domain: Domain):
endpoint_config = EndpointConfig(
type="tests.core.test_tracker_stores.NonAsyncTrackerStore"
)
with pytest.warns(FutureWarning):
tracker_store = TrackerStore.create(endpoint_config)
assert isinstance(tracker_store, AwaitableTrackerStore)
assert isinstance(tracker_store._tracker_store, NonAsyncTrackerStore)
def test_create_awaitable_tracker_store_with_endpoint_config():
endpoint_config = EndpointConfig(
type="tests.core.test_tracker_stores.NonAsyncTrackerStore"
)
tracker_store = AwaitableTrackerStore.create(endpoint_config)
assert isinstance(tracker_store, AwaitableTrackerStore)
assert isinstance(tracker_store._tracker_store, NonAsyncTrackerStore)
@pytest.mark.parametrize(
"endpoints_file, expected_type",
[
(None, InMemoryTrackerStore),
("data/test_endpoints/endpoints_sql.yml", SQLTrackerStore),
("data/test_endpoints/endpoints_redis.yml", RedisTrackerStore),
],
)
def test_create_tracker_store_from_endpoints_file(
endpoints_file: Optional[Text], expected_type: Any, domain: Domain
) -> None:
endpoint_config = read_endpoint_config(endpoints_file, "tracker_store")
tracker_store = rasa.core.tracker_store.create_tracker_store(
endpoint_config, domain
)
assert rasa.core.tracker_store.check_if_tracker_store_async(tracker_store) is True
assert isinstance(tracker_store, expected_type)
async def test_fail_safe_tracker_store_retrieve_full_tracker(
domain: Domain, tracker_with_restarted_event: DialogueStateTracker
) -> None:
primary_tracker_store = SQLTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
tracker_store = FailSafeTrackerStore(primary_tracker_store)
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve_full_tracker(sender_id)
assert tracker == tracker_with_restarted_event
async def test_fail_safe_tracker_store_retrieve_full_tracker_with_exception(
caplog: LogCaptureFixture,
) -> None:
primary_tracker_store = MagicMock()
primary_tracker_store.domain = Domain.empty()
primary_tracker_store.event_broker = None
exception = Exception("Something went wrong")
primary_tracker_store.retrieve_full_tracker = AsyncMock(side_effect=exception)
tracker_store = FailSafeTrackerStore(primary_tracker_store)
with caplog.at_level(logging.ERROR):
await tracker_store.retrieve_full_tracker("some_id")
assert "Error happened when trying to retrieve conversation tracker" in caplog.text
assert f"Please investigate the following error: {exception}." in caplog.text
async def test_sql_get_or_create_full_tracker_without_action_listen() -> None:
tracker_store = SQLTrackerStore(Domain.empty())
sender_id = uuid.uuid4().hex
tracker = await tracker_store.get_or_create_full_tracker(
sender_id=sender_id, append_action_listen=False
)
assert tracker.sender_id == sender_id
assert tracker.events == deque()
async def test_sql_get_or_create_full_tracker_with_action_listen() -> None:
tracker_store = SQLTrackerStore(Domain.empty())
sender_id = uuid.uuid4().hex
tracker = await tracker_store.get_or_create_full_tracker(
sender_id=sender_id, append_action_listen=True
)
assert tracker.sender_id == sender_id
assert tracker.events == deque([ActionExecuted(ACTION_LISTEN_NAME)])
async def test_sql_get_or_create_full_tracker_with_existing_tracker(
tracker_with_restarted_event: DialogueStateTracker,
) -> None:
sender_id = tracker_with_restarted_event.sender_id
tracker_store = SQLTrackerStore(Domain.empty())
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.get_or_create_full_tracker(
sender_id=sender_id, append_action_listen=False
)
assert tracker.sender_id == sender_id
assert tracker.events == deque(tracker_with_restarted_event.events)
async def test_sql_tracker_store_retrieve_full_tracker(
domain: Domain, tracker_with_restarted_event: DialogueStateTracker
) -> None:
tracker_store = SQLTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve_full_tracker(sender_id)
assert tracker == tracker_with_restarted_event
async def test_sql_tracker_store_retrieve(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
events_after_restart: List[Event],
) -> None:
tracker_store = SQLTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve(sender_id)
# the retrieved tracker with the latest session would not contain
# `action_session_start` event because the SQLTrackerStore filters
# only the events after `session_started` event
assert list(tracker.events) == events_after_restart[1:]
async def test_in_memory_tracker_store_retrieve_full_tracker(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
) -> None:
tracker_store = InMemoryTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve_full_tracker(sender_id)
assert tracker == tracker_with_restarted_event
async def test_in_memory_tracker_store_retrieve(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
events_after_restart: List[Event],
) -> None:
tracker_store = InMemoryTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve(sender_id)
assert list(tracker.events) == events_after_restart
async def test_mongo_tracker_store_retrieve_full_tracker(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
) -> None:
tracker_store = MockedMongoTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve_full_tracker(sender_id)
assert tracker == tracker_with_restarted_event
async def test_mongo_tracker_store_retrieve(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
events_after_restart: List[Event],
) -> None:
tracker_store = MockedMongoTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve(sender_id)
# the retrieved tracker with the latest session would not contain
# `action_session_start` event because the MongoTrackerStore filters
# only the events after `session_started` event
assert list(tracker.events) == events_after_restart[1:]
class MockedRedisTrackerStore(RedisTrackerStore):
def __init__(
self,
domain: Domain,
) -> None:
self.red = fakeredis.FakeStrictRedis()
self.key_prefix = DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
self.record_exp = None
super(RedisTrackerStore, self).__init__(domain, None)
async def test_redis_tracker_store_retrieve_full_tracker(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
) -> None:
tracker_store = MockedRedisTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve_full_tracker(sender_id)
assert tracker == tracker_with_restarted_event
async def test_redis_tracker_store_retrieve(
domain: Domain,
tracker_with_restarted_event: DialogueStateTracker,
events_after_restart: List[Event],
) -> None:
tracker_store = MockedRedisTrackerStore(domain)
sender_id = tracker_with_restarted_event.sender_id
await tracker_store.save(tracker_with_restarted_event)
tracker = await tracker_store.retrieve(sender_id)
assert list(tracker.events) == events_after_restart
async def test_redis_tracker_store_merge_trackers_same_session() -> None:
start_session_sequence = [
ActionExecuted(ACTION_SESSION_START_NAME),
SessionStarted(),
ActionExecuted(ACTION_LISTEN_NAME),
]
events: List[Event] = start_session_sequence + [UserUttered("hello")]
prior_tracker = DialogueStateTracker.from_events(
"same-session",
evts=events,
)
events += [BotUttered("Hey! How can I help you?")]
new_tracker = DialogueStateTracker.from_events(
"same-session",
evts=events,
)
actual_tracker = RedisTrackerStore._merge_trackers(prior_tracker, new_tracker)
assert actual_tracker == new_tracker
def test_redis_tracker_store_merge_trackers_overlapping_session() -> None:
prior_tracker_events: List[Event] = [
ActionExecuted(ACTION_SESSION_START_NAME, timestamp=1),
SessionStarted(timestamp=2),
ActionExecuted(ACTION_LISTEN_NAME, timestamp=3),
UserUttered("hello", timestamp=4),
BotUttered("Hey! How can I help you?", timestamp=5),
UserUttered("/restart", timestamp=6),
ActionExecuted(ACTION_RESTART_NAME, timestamp=7),
]
new_start_session = [
ActionExecuted(ACTION_SESSION_START_NAME, timestamp=8),
SessionStarted(timestamp=9),
ActionExecuted(ACTION_LISTEN_NAME, timestamp=10),
]
prior_tracker_events += new_start_session
prior_tracker = DialogueStateTracker.from_events(
"overlapping-session",
evts=prior_tracker_events,
)
after_restart_event = [UserUttered("hi again", timestamp=11)]
new_tracker_events = new_start_session + after_restart_event
new_tracker = DialogueStateTracker.from_events(
"overlapping-session",
evts=new_tracker_events,
)
actual_tracker = RedisTrackerStore._merge_trackers(prior_tracker, new_tracker)
expected_events = prior_tracker_events + after_restart_event
assert list(actual_tracker.events) == expected_events
def test_redis_tracker_store_merge_trackers_different_session() -> None:
prior_tracker_events: List[Event] = [
ActionExecuted(ACTION_SESSION_START_NAME, timestamp=1),
SessionStarted(timestamp=2),
ActionExecuted(ACTION_LISTEN_NAME, timestamp=3),
UserUttered("hello", timestamp=4),
BotUttered("Hey! How can I help you?", timestamp=5),
]
prior_tracker = DialogueStateTracker.from_events(
"different-session",
evts=prior_tracker_events,
)
new_session = [
ActionExecuted(ACTION_SESSION_START_NAME, timestamp=8),
SessionStarted(timestamp=9),
ActionExecuted(ACTION_LISTEN_NAME, timestamp=10),
UserUttered("I need help.", timestamp=11),
]
new_tracker = DialogueStateTracker.from_events(
"different-session",
evts=new_session,
)
actual_tracker = RedisTrackerStore._merge_trackers(prior_tracker, new_tracker)
expected_events = prior_tracker_events + new_session
assert list(actual_tracker.events) == expected_events
async def test_tracker_event_diff_engine_event_difference() -> None:
start_session_sequence = [
ActionExecuted(ACTION_SESSION_START_NAME),
SessionStarted(),
ActionExecuted(ACTION_LISTEN_NAME),
]
events: List[Event] = start_session_sequence + [UserUttered("hello")]
prior_tracker = DialogueStateTracker.from_events(
"same-session",
evts=events,
)
new_events = [BotUttered("Hey! How can I help you?")]
events += new_events
new_tracker = DialogueStateTracker.from_events(
"same-session",
evts=events,
)
event_diff = TrackerEventDiffEngine.event_difference(prior_tracker, new_tracker)
assert new_events == event_diff