Repository: hikari-py/hikari (python)

Issue:
Handle unavailable guilds in GUILD_CREATE
### Steps to reproduce

Join an unavailable guild

### Expected result

Torn between fully ignoring the event or calling `on_guild_delete` so it dispatches a `GuildUnavailableEvent`

### Actual result

![image](https://user-images.githubusercontent.com/29100934/170681523-0e007f2b-0b17-4bd1-9a9d-82cae1827be6.png)

### System info

```shell
hikari (2.0.0.dev109) [HEAD]
located at /home/davfsa/coding/hikari/.venv/lib/python3.10/site-packages/hikari
CPython 3.10.0 GCC 10.2.1 20210110
```


### Further info

_No response_

### Checklist

- [X] I have made sure to remove ANY sensitive information (bot token, passwords, credentials, personal details, etc.).
- [X] I have searched the issue tracker and have made sure it's not a duplicate. If it is a follow up of another issue, I have specified it.

Pull request:
Ignore GUILD_CREATE which contain unavailable guilds
### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.

### Related issues
Fixes #1172


Patch:
diff --git a/changes/1284.bugfix.md b/changes/1284.bugfix.md
new file mode 100644
index 00000000..6b8986de
--- /dev/null
+++ b/changes/1284.bugfix.md
@@ -0,0 +1,1 @@
+Ignore guild create events which contain unavailable guilds
diff --git a/dev-requirements/safety.txt b/dev-requirements/safety.txt
index c4d6f390..de1f816a 100644
--- a/dev-requirements/safety.txt
+++ b/dev-requirements/safety.txt
@@ -1,4 +1,4 @@
-safety~=2.1.1
+safety~=2.2.0
 
 # Temporary addition to avoid safety erroring due to https://github.com/pypa/pip/pull/9827
 pip>=21.1
diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py
index 0af15177..4e159f85 100644
--- a/hikari/impl/event_manager.py
+++ b/hikari/impl/event_manager.py
@@ -180,9 +180,17 @@ async def on_guild_create(  # noqa: C901 - Function too complex
         """See https://discord.com/developers/docs/topics/gateway#guild-create for more info."""
         event: typing.Union[guild_events.GuildAvailableEvent, guild_events.GuildJoinEvent, None]
 
-        if "unavailable" in payload and self._enabled_for_event(guild_events.GuildAvailableEvent):
+        unavailable = payload.get("unavailable")
+
+        if unavailable:
+            # It is possible for GUILD_CREATE to contain unavailable guilds during outages
+            # In cases like this, we can just ignore the event and wait for the outage to
+            # be resolved, where the correct guild visibility event will be dispatched
+            return
+
+        if unavailable is not None and self._enabled_for_event(guild_events.GuildAvailableEvent):
             event = self._event_factory.deserialize_guild_available_event(shard, payload)
-        elif "unavailable" not in payload and self._enabled_for_event(guild_events.GuildJoinEvent):
+        elif unavailable is None and self._enabled_for_event(guild_events.GuildJoinEvent):
             event = self._event_factory.deserialize_guild_join_event(shard, payload)
         else:
             event = None


Test patch:
diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py
index 1374433b..50839974 100644
--- a/tests/hikari/impl/test_event_manager.py
+++ b/tests/hikari/impl/test_event_manager.py
@@ -246,12 +246,46 @@ async def test_on_channel_pins_update(self, stateless_event_manager_impl, shard,
             event_factory.deserialize_channel_pins_update_event.return_value
         )
 
-    @pytest.mark.parametrize("unavailable", [True, False])
+    @pytest.mark.asyncio()
+    async def test_on_guild_create_when_unavailable_guild(
+        self, event_manager_impl, shard, event_factory, entity_factory
+    ):
+        payload = {"unavailable": True}
+        event_manager_impl._cache_enabled_for = mock.Mock(return_value=True)
+        event_manager_impl._enabled_for_event = mock.Mock(return_value=True)
+
+        with mock.patch.object(event_manager, "_request_guild_members") as request_guild_members:
+            await event_manager_impl.on_guild_create(shard, payload)
+
+        event_manager_impl._enabled_for_event.assert_not_called()
+        event_factory.deserialize_guild_available_event.assert_not_called()
+        event_factory.deserialize_guild_join_event.assert_not_called()
+
+        event_manager_impl._cache.update_guild.assert_not_called()
+        event_manager_impl._cache.clear_guild_channels_for_guild.assert_not_called()
+        event_manager_impl._cache.set_guild_channel.assert_not_called()
+        event_manager_impl._cache.clear_emojis_for_guild.assert_not_called()
+        event_manager_impl._cache.set_emoji.assert_not_called()
+        event_manager_impl._cache.clear_stickers_for_guild.assert_not_called()
+        event_manager_impl._cache.set_sticker.assert_not_called()
+        event_manager_impl._cache.clear_roles_for_guild.assert_not_called()
+        event_manager_impl._cache.set_role.assert_not_called()
+        event_manager_impl._cache.clear_members_for_guild.assert_not_called()
+        event_manager_impl._cache.set_member.assert_not_called()
+        event_manager_impl._cache.clear_presences_for_guild.assert_not_called()
+        event_manager_impl._cache.set_presence.assert_not_called()
+        event_manager_impl._cache.clear_voice_states_for_guild.assert_not_called()
+        event_manager_impl._cache.set_voice_state.assert_not_called()
+        request_guild_members.assert_not_called()
+
+        event_manager_impl.dispatch.assert_not_called()
+
+    @pytest.mark.parametrize("include_unavailable", [True, False])
     @pytest.mark.asyncio()
     async def test_on_guild_create_when_dispatching_and_not_caching(
-        self, event_manager_impl, shard, event_factory, entity_factory, unavailable
+        self, event_manager_impl, shard, event_factory, entity_factory, include_unavailable
     ):
-        payload = {"unavailable": False} if unavailable else {}
+        payload = {"unavailable": False} if include_unavailable else {}
         event_manager_impl._intents = intents.Intents.NONE
         event_manager_impl._cache_enabled_for = mock.Mock(return_value=False)
         event_manager_impl._enabled_for_event = mock.Mock(return_value=True)
@@ -259,7 +293,7 @@ async def test_on_guild_create_when_dispatching_and_not_caching(
         with mock.patch.object(event_manager, "_request_guild_members") as request_guild_members:
             await event_manager_impl.on_guild_create(shard, payload)
 
-        if unavailable:
+        if include_unavailable:
             event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildAvailableEvent)
             event_factory.deserialize_guild_available_event.assert_called_once_with(shard, payload)
             event = event_factory.deserialize_guild_available_event.return_value
@@ -287,12 +321,12 @@ async def test_on_guild_create_when_dispatching_and_not_caching(
 
         event_manager_impl.dispatch.assert_awaited_once_with(event)
 
-    @pytest.mark.parametrize("unavailable", [True, False])
+    @pytest.mark.parametrize("include_unavailable", [True, False])
     @pytest.mark.asyncio()
     async def test_on_guild_create_when_not_dispatching_and_not_caching(
-        self, event_manager_impl, shard, event_factory, entity_factory, unavailable
+        self, event_manager_impl, shard, event_factory, entity_factory, include_unavailable
     ):
-        payload = {"unavailable": False} if unavailable else {}
+        payload = {"unavailable": False} if include_unavailable else {}
         event_manager_impl._intents = intents.Intents.NONE
         event_manager_impl._cache_enabled_for = mock.Mock(return_value=False)
         event_manager_impl._enabled_for_event = mock.Mock(return_value=False)
@@ -300,7 +334,7 @@ async def test_on_guild_create_when_not_dispatching_and_not_caching(
         with mock.patch.object(event_manager, "_request_guild_members") as request_guild_members:
             await event_manager_impl.on_guild_create(shard, payload)
 
-        if unavailable:
+        if include_unavailable:
             event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildAvailableEvent)
         else:
             event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildJoinEvent)
@@ -327,12 +361,12 @@ async def test_on_guild_create_when_not_dispatching_and_not_caching(
 
         event_manager_impl.dispatch.assert_not_called()
 
-    @pytest.mark.parametrize("unavailable", [True, False])
+    @pytest.mark.parametrize("include_unavailable", [True, False])
     @pytest.mark.asyncio()
     async def test_on_guild_create_when_not_dispatching_and_caching(
-        self, event_manager_impl, shard, event_factory, entity_factory, unavailable
+        self, event_manager_impl, shard, event_factory, entity_factory, include_unavailable
     ):
-        payload = {"unavailable": False} if unavailable else {}
+        payload = {"unavailable": False} if include_unavailable else {}
         event_manager_impl._intents = intents.Intents.NONE
         event_manager_impl._cache_enabled_for = mock.Mock(return_value=True)
         event_manager_impl._enabled_for_event = mock.Mock(return_value=False)
@@ -348,7 +382,7 @@ async def test_on_guild_create_when_not_dispatching_and_caching(
         with mock.patch.object(event_manager, "_request_guild_members") as request_guild_members:
             await event_manager_impl.on_guild_create(shard, payload)
 
-        if unavailable:
+        if include_unavailable:
             event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildAvailableEvent)
         else:
             event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildJoinEvent)
@@ -375,13 +409,13 @@ async def test_on_guild_create_when_not_dispatching_and_caching(
 
         event_manager_impl.dispatch.assert_not_called()
 
-    @pytest.mark.parametrize("unavailable", [True, False])
+    @pytest.mark.parametrize("include_unavailable", [True, False])
     @pytest.mark.asyncio()
     async def test_on_guild_create_when_stateless(
-        self, stateless_event_manager_impl, shard, event_factory, entity_factory, unavailable
+        self, stateless_event_manager_impl, shard, event_factory, entity_factory, include_unavailable
     ):
         payload = {"id": 123}
-        if unavailable:
+        if include_unavailable:
             payload["unavailable"] = False
 
         stateless_event_manager_impl._intents = intents.Intents.NONE
@@ -391,7 +425,7 @@ async def test_on_guild_create_when_stateless(
         with mock.patch.object(event_manager, "_request_guild_members") as request_guild_members:
             await stateless_event_manager_impl.on_guild_create(shard, payload)
 
-        if unavailable:
+        if include_unavailable:
             stateless_event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildAvailableEvent)
         else:
             stateless_event_manager_impl._enabled_for_event.assert_called_once_with(guild_events.GuildJoinEvent)