Repository: rasahq/rasa
Issue: slots extracted `from_intent` for an un-specified intent raises no error/warning

### Rasa Open Source version

3.0.0rc1

### Rasa SDK version

_No response_

### Rasa X version

_No response_

### Python version

3.8

### What operating system are you using?

OSX

### What happened?

Start with a `rasa init` project and specify a slot that should be extracted from an intent that does not exist (i.e. not specified in domain / data). Expected some error message but the slot was just not extracted. (Accidentally encountered that when using "agree" instead of "affirm" as intent).

### Command / Request

_No response_

### Relevant log output

_No response_

Changed files:
rasa/core/actions/action.py, tests/core/test_actions.py, rasa/shared/core/slot_mappings.py, tests/core/actions/test_forms.py

diff --git a/rasa/core/actions/action.py b/rasa/core/actions/action.py
index dcc2923089b..ed99d182c44 100644
--- a/rasa/core/actions/action.py
+++ b/rasa/core/actions/action.py
@@ -1154,6 +1154,9 @@ class ActionExtractSlots(Action):
 
         for slot in user_slots:
             for mapping in slot.mappings:
+                if not SlotMapping.check_mapping_validity(slot.name, mapping, domain):
+                    continue
+
                 intent_is_desired = SlotMapping.intent_is_desired(
                     mapping, tracker, domain
                 )
diff --git a/rasa/shared/core/slot_mappings.py b/rasa/shared/core/slot_mappings.py
index c91fc6e8bb1..fa171749b7d 100644
--- a/rasa/shared/core/slot_mappings.py
+++ b/rasa/shared/core/slot_mappings.py
@@ -167,6 +167,45 @@ class SlotMapping(Enum):
 
         return slot_fulfils_entity_mapping
 
+    @staticmethod
+    def check_mapping_validity(
+        slot_name: Text, mapping: Dict[Text, Any], domain: "Domain"
+    ) -> bool:
+        """Checks the mapping for validity.
+
+        Args:
+            mapping: Slot mapping.
+            domain: The domain to check against.
+
+        Returns:
+            True, if intent and entity specified in a mapping exist in domain.
+        """
+        from rasa.shared.core.constants import MAPPING_TYPE
+
+        if (
+            mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_ENTITY)
+            and mapping.get(ENTITY_ATTRIBUTE_TYPE) not in domain.entities
+        ):
+            rasa.shared.utils.io.raise_warning(
+                f"Slot '{slot_name}' uses a 'from_entity' mapping "
+                f"for a non-existent entity '{mapping.get(ENTITY_ATTRIBUTE_TYPE)}'. "
+                f"Skipping slot extraction because of invalid mapping."
+            )
+            return False
+
+        if (
+            mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_INTENT)
+            and mapping.get("intent") not in domain.intents
+        ):
+            rasa.shared.utils.io.raise_warning(
+                f"Slot '{slot_name}' uses a 'from_intent' mapping for "
+                f"a non-existent intent '{mapping.get('intent')}'. "
+                f"Skipping slot extraction because of invalid mapping."
+            )
+            return False
+
+        return True
+
 
 def validate_slot_mappings(domain_slots: Dict[Text, Any]) -> None:
     """Raises InvalidDomain exception if slot mappings are invalid."""
diff --git a/tests/core/actions/test_forms.py b/tests/core/actions/test_forms.py
index aefc0040672..27764773bd7 100644
--- a/tests/core/actions/test_forms.py
+++ b/tests/core/actions/test_forms.py
@@ -472,6 +472,14 @@ async def test_validate_slots(
     tracker = DialogueStateTracker.from_events(sender_id="bla", evts=events)
 
     domain = f"""
+    version: "3.0"
+
+    entities:
+    - num_tables
+    - some_entity
+    - some_other_entity
+    - some_slot
+
     slots:
       {slot_name}:
         type: any
@@ -482,15 +490,17 @@ async def test_validate_slots(
         mappings:
         - type: from_entity
           entity: num_tables
+
     forms:
       {form_name}:
         {REQUIRED_SLOTS_KEY}:
             - {slot_name}
             - num_tables
+
     actions:
     - validate_{form_name}
     """
-    domain = Domain.from_yaml(domain)
+    domain = Domain.from_yaml(textwrap.dedent(domain))
     action_extract_slots = ActionExtractSlots(action_endpoint=None)
 
     slot_events = await action_extract_slots.run(
diff --git a/tests/core/test_actions.py b/tests/core/test_actions.py
index 21e531cd2f4..2b695de0441 100644
--- a/tests/core/test_actions.py
+++ b/tests/core/test_actions.py
@@ -1159,6 +1159,7 @@ async def test_action_extract_slots_predefined_mappings(
             - deny
             entities:
             - city
+            - name
             slots:
               location:
                 type: text
@@ -1194,12 +1195,14 @@ async def test_action_extract_slots_predefined_mappings(
 
     action_extract_slots = ActionExtractSlots(action_endpoint=None)
     tracker = DialogueStateTracker.from_events("sender", evts=[user])
-    events = await action_extract_slots.run(
-        CollectingOutputChannel(),
-        TemplatedNaturalLanguageGenerator(domain.responses),
-        tracker,
-        domain,
-    )
+
+    with pytest.warns(None):
+        events = await action_extract_slots.run(
+            CollectingOutputChannel(),
+            TemplatedNaturalLanguageGenerator(domain.responses),
+            tracker,
+            domain,
+        )
 
     assert events == [SlotSet(slot_name, slot_value)]
 
@@ -1296,6 +1299,8 @@ async def test_action_extract_slots_when_mapping_applies(
 
     domain = Domain.from_dict(
         {
+            "intents": ["greet"],
+            "entities": ["some_slot"],
             "slots": {entity_name: {"type": "text", "mappings": [slot_mapping]}},
             "forms": {form_name: {REQUIRED_SLOTS_KEY: [entity_name]}},
         }
@@ -1351,6 +1356,9 @@ async def test_action_extract_slots_with_list_slot(
             f"""
     version: "3.0"
 
+    entities:
+    - topping
+
     slots:
       {slot_name}:
         type: list
@@ -1690,6 +1698,7 @@ async def test_action_extract_slots_from_entity(
     )
     domain = Domain.from_dict(
         {
+            "entities": ["some_entity"],
             "slots": {"some_slot": {"type": "any", "mappings": [mapping],}},
             "forms": {form_name: {REQUIRED_SLOTS_KEY: ["some_slot"]}},
         }
@@ -1743,6 +1752,14 @@ async def test_extract_other_list_slot_from_entity(
             f"""
     version: "3.0"
 
+    entities:
+    - topping
+    - some_slot
+
+    intents:
+    - some_intent
+    - greeted
+
     slots:
       {slot_name}:
         type: list
@@ -2289,15 +2306,93 @@ async def test_action_extract_slots_with_empty_conditions():
 
     action_extract_slots = ActionExtractSlots(None)
 
-    events = await action_extract_slots.run(
-        CollectingOutputChannel(),
-        TemplatedNaturalLanguageGenerator(domain.responses),
-        tracker,
-        domain,
-    )
+    with pytest.warns(None):
+        events = await action_extract_slots.run(
+            CollectingOutputChannel(),
+            TemplatedNaturalLanguageGenerator(domain.responses),
+            tracker,
+            domain,
+        )
     assert events == [SlotSet("location", "Berlin")]
 
 
+async def test_action_extract_slots_with_not_existing_entity():
+    domain_yaml = textwrap.dedent(
+        """
+        version: "3.0"
+
+        entities:
+        - city
+
+        slots:
+          location:
+            type: float
+            influence_conversation: false
+            mappings:
+            - type: from_entity
+              entity: city2
+        """
+    )
+    domain = Domain.from_yaml(domain_yaml)
+    event = UserUttered("Hi", entities=[{"entity": "city", "value": "Berlin"}])
+    tracker = DialogueStateTracker.from_events(sender_id="test_id", evts=[event])
+
+    action_extract_slots = ActionExtractSlots(None)
+
+    with pytest.warns(
+        None,
+        match="Slot 'location' uses a `from_entity` mapping for "
+        "a non-existent entity 'city2'. "
+        "Skipping slot extraction because of invalid mapping.",
+    ):
+        events = await action_extract_slots.run(
+            CollectingOutputChannel(),
+            TemplatedNaturalLanguageGenerator(domain.responses),
+            tracker,
+            domain,
+        )
+    assert events == []
+
+
+async def test_action_extract_slots_with_not_existing_intent():
+    domain_yaml = textwrap.dedent(
+        """
+        version: "3.0"
+
+        intents:
+        - greet
+
+        slots:
+          location:
+            type: text
+            influence_conversation: false
+            mappings:
+            - type: from_intent
+              intent: affirm
+              value: some_value
+        """
+    )
+    domain = Domain.from_yaml(domain_yaml)
+    event = UserUttered("Hi", entities=[{"entity": "city", "value": "Berlin"}])
+    tracker = DialogueStateTracker.from_events(sender_id="test_id", evts=[event])
+
+    action_extract_slots = ActionExtractSlots(None)
+
+    with pytest.warns(
+        UserWarning,
+        match=r"Slot 'location' uses a 'from_intent' mapping for "
+        r"a non-existent intent 'affirm'. "
+        r"Skipping slot extraction because of invalid mapping.",
+    ):
+        events = await action_extract_slots.run(
+            CollectingOutputChannel(),
+            TemplatedNaturalLanguageGenerator(domain.responses),
+            tracker,
+            domain,
+        )
+    assert events == []
+
+
 async def test_action_extract_slots_with_none_value_predefined_mapping():
     domain_yaml = textwrap.dedent(
         """