Beispiel #1
0
    def test_events(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )

        async def event_handler():
            pass

        app.event("app_mention")(event_handler)

        input = {
            "token": "verification_token",
            "team_id": "T111",
            "enterprise_id": "E111",
            "api_app_id": "A111",
            "event": {
                "client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63",
                "type": "app_mention",
                "text": "<@W111> Hi there!",
                "user": "******",
                "ts": "1595926230.009600",
                "team": "T111",
                "channel": "C111",
                "event_ts": "1595926230.009600",
            },
            "type": "event_callback",
            "event_id": "Ev111",
            "event_time": 1595926230,
            "authed_users": ["W111"],
        }
        timestamp, body = str(int(time())), json.dumps(input)

        api = FastAPI()
        app_handler = AsyncSlackRequestHandler(app)

        @api.post("/slack/events")
        async def endpoint(req: Request):
            return await app_handler.handle(req)

        client = TestClient(api)
        response = client.post(
            "/slack/events",
            data=body,
            headers=self.build_headers(timestamp, body),
        )
        assert response.status_code == 200
        assert_auth_test_count(self, 1)
Beispiel #2
0
    async def test_self_events(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
            authorize=authorize,
        )
        app.event("reaction_added")(whats_up)

        self_event = {
            "token": "verification_token",
            "team_id": "T_SOURCE",
            "enterprise_id": "E_SOURCE",
            "api_app_id": "A111",
            "event": {
                "type": "reaction_added",
                "user": "******",  # bot_user_id
                "item": {
                    "type": "message",
                    "channel": "C111",
                    "ts": "1599529504.000400",
                },
                "reaction": "heart_eyes",
                "item_user": "******",
                "event_ts": "1599616881.000800",
            },
            "type": "event_callback",
            "event_id": "Ev111",
            "event_time": 1599616881,
            "authorizations": [
                {
                    "enterprise_id": "E_INSTALLED",
                    "team_id": "T_INSTALLED",
                    "user_id": "W111",
                    "is_bot": True,
                    "is_enterprise_install": False,
                }
            ],
        }
        timestamp, body = str(int(time())), json.dumps(self_event)
        request = AsyncBoltRequest(
            body=body, headers=self.build_headers(timestamp, body)
        )
        response = await app.async_dispatch(request)
        assert response.status == 200
        await assert_auth_test_count_async(self, 1)
        await asyncio.sleep(1)  # wait a bit after auto ack()
        # The listener should not be executed
        assert self.mock_received_requests.get("/chat.postMessage") is None
Beispiel #3
0
    def test_shortcuts(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )

        async def shortcut_handler(ack):
            await ack()

        app.shortcut("test-shortcut")(shortcut_handler)

        input = {
            "type": "shortcut",
            "token": "verification_token",
            "action_ts": "111.111",
            "team": {
                "id": "T111",
                "domain": "workspace-domain",
                "enterprise_id": "E111",
                "enterprise_name": "Org Name",
            },
            "user": {
                "id": "W111",
                "username": "******",
                "team_id": "T111"
            },
            "callback_id": "test-shortcut",
            "trigger_id": "111.111.xxxxxx",
        }

        timestamp, body = str(int(
            time())), f"payload={quote(json.dumps(input))}"

        api = FastAPI()
        app_handler = AsyncSlackRequestHandler(app)

        @api.post("/slack/events")
        async def endpoint(req: Request):
            return await app_handler.handle(req)

        client = TestClient(api)
        response = client.post(
            "/slack/events",
            data=body,
            headers=self.build_headers(timestamp, body),
        )
        assert response.status_code == 200
        assert_auth_test_count(self, 1)
Beispiel #4
0
    def test_oauth(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
            oauth_settings=AsyncOAuthSettings(
                client_id="111.111",
                client_secret="xxx",
                scopes=["chat:write", "commands"],
            ),
        )
        app_handler = AsyncSlackRequestHandler(app)

        async def endpoint(req: Request):
            return await app_handler.handle(req)

        api = Starlette(
            debug=True,
            routes=[
                Route("/slack/install", endpoint=endpoint, methods=["GET"])
            ],
        )

        client = TestClient(api)
        response = client.get("/slack/install", allow_redirects=False)
        assert response.status_code == 200
        assert response.headers.get(
            "content-type") == "text/html; charset=utf-8"
        assert response.headers.get("content-length") == "565"
        assert "https://slack.com/oauth/v2/authorize?state=" in response.text
Beispiel #5
0
    async def test_message_subtypes_3(self):
        app = AsyncApp(client=self.web_client,
                       signing_secret=self.signing_secret)
        app._client = AsyncWebClient(token="uninstalled-revoked",
                                     base_url=self.mock_api_server_base_url)

        @app.event("message")
        async def handler1(event):
            assert event["subtype"] == "file_share"

        timestamp, body = str(int(time())), json.dumps(
            self.message_file_share_body)
        request: AsyncBoltRequest = AsyncBoltRequest(
            body=body, headers=self.build_headers(timestamp, body))
        response = await app.async_dispatch(request)
        assert response.status == 200
Beispiel #6
0
    async def test_string_keyword(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        result = {"call_count": 0}

        @app.message("Hi there!")
        async def handle_messages(event, logger):
            logger.info(event)
            result["call_count"] = result["call_count"] + 1

        request = self.build_request(user_message_event_payload)
        response = await app.async_dispatch(request)
        assert response.status == 200

        request = self.build_request(bot_message_event_payload)
        response = await app.async_dispatch(request)
        assert response.status == 200

        request = self.build_request(classic_bot_message_event_payload)
        response = await app.async_dispatch(request)
        assert response.status == 200

        assert self.mock_received_requests["/auth.test"] == 1
        await asyncio.sleep(1)  # wait a bit after auto ack()
        assert result["call_count"] == 3
Beispiel #7
0
    async def test_success_global_2(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        app.global_shortcut("test-shortcut")(simple_listener)

        request = self.build_valid_request(global_shortcut_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 200
        assert self.mock_received_requests["/auth.test"] == 1

        request = self.build_valid_request(message_shortcut_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 2
    async def test_default(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        app.event("app_mention")(whats_up)

        timestamp, body = str(int(time())), json.dumps(app_mention_body)
        request = AsyncBoltRequest(
            body=body, headers=self.build_headers(timestamp, body)
        )
        response = await app.async_dispatch(request)
        assert response.status == 200
        await assert_auth_test_count_async(self, 1)
        await asyncio.sleep(1)  # wait a bit after auto ack()
        assert self.mock_received_requests["/chat.postMessage"] == 1
Beispiel #9
0
    async def test_success_global_2(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        app.global_shortcut("test-shortcut")(simple_listener)

        request = self.build_valid_request(global_shortcut_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 200
        await assert_auth_test_count_async(self, 1)

        request = self.build_valid_request(message_shortcut_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 404
        await assert_auth_test_count_async(self, 1)
    async def test_handle_callback(self):
        oauth_flow = AsyncOAuthFlow(
            client=AsyncWebClient(base_url=self.mock_api_server_base_url),
            settings=AsyncOAuthSettings(
                client_id="111.222",
                client_secret="xxx",
                scopes=["chat:write", "commands"],
                installation_store=FileInstallationStore(),
                state_store=FileOAuthStateStore(expiration_seconds=120),
                success_url="https://www.example.com/completion",
                failure_url="https://www.example.com/failure",
            ),
        )
        state = await oauth_flow.issue_new_state(None)
        req = AsyncBoltRequest(
            body="",
            query=f"code=foo&state={state}",
            headers={
                "cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]
            },
        )
        resp = await oauth_flow.handle_callback(req)
        assert resp.status == 200
        assert "https://www.example.com/completion" in resp.body

        app = AsyncApp(signing_secret="signing_secret", oauth_flow=oauth_flow)
        global_shortcut_body = {
            "type": "shortcut",
            "token": "verification_token",
            "action_ts": "111.111",
            "team": {
                "id": "T111",
                "domain": "workspace-domain",
                "enterprise_id": "E111",
                "enterprise_name": "Org Name",
            },
            "user": {
                "id": "W111",
                "username": "******",
                "team_id": "T111"
            },
            "callback_id": "test-shortcut",
            "trigger_id": "111.111.xxxxxx",
        }
        body = f"payload={quote(json.dumps(global_shortcut_body))}"
        timestamp = str(int(time()))
        signature_verifier = SignatureVerifier("signing_secret")
        headers = {
            "content-type": ["application/x-www-form-urlencoded"],
            "x-slack-signature": [
                signature_verifier.generate_signature(body=body,
                                                      timestamp=timestamp)
            ],
            "x-slack-request-timestamp": [timestamp],
        }
        request = AsyncBoltRequest(body=body, headers=headers)
        response = await app.async_dispatch(request)
        assert response.status == 200
        await assert_auth_test_count_async(self, 1)
Beispiel #11
0
    def test_commands(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )

        async def command_handler(ack):
            await ack()

        app.command("/hello-world")(command_handler)

        input = (
            "token=verification_token"
            "&team_id=T111"
            "&team_domain=test-domain"
            "&channel_id=C111"
            "&channel_name=random"
            "&user_id=W111"
            "&user_name=primary-owner"
            "&command=%2Fhello-world"
            "&text=Hi"
            "&enterprise_id=E111"
            "&enterprise_name=Org+Name"
            "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx"
            "&trigger_id=111.111.xxx")
        timestamp, body = str(int(time())), input

        async def endpoint(req: Request):
            return await app_handler.handle(req)

        api = Starlette(
            debug=True,
            routes=[
                Route("/slack/events", endpoint=endpoint, methods=["POST"])
            ],
        )
        app_handler = AsyncSlackRequestHandler(app)

        client = TestClient(api)
        response = client.post(
            "/slack/events",
            data=body,
            headers=self.build_headers(timestamp, body),
        )
        assert response.status_code == 200
        assert self.mock_received_requests["/auth.test"] == 1
Beispiel #12
0
    async def test_failure(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        request = self.build_valid_request()
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 1

        app.action({
            "callback_id": "unknown",
            "type": "interactive_message",
        })(simple_listener)
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 1
Beispiel #13
0
    async def test_failure(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        request = self.build_valid_request()
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 1

        app.view({
            "type": "view_closed",
            "callback_id": "view-idddd"
        })(simple_listener)
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 2
Beispiel #14
0
    async def test_cancellation_failure_2(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        request = self.build_valid_request(suggestion_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 404
        await assert_auth_test_count_async(self, 1)

        app.action({
            "type": "dialog_cancellation",
            "callback_id": "dialog-callback-iddddd"
        })(handle_cancellation)
        response = await app.async_dispatch(request)
        assert response.status == 404
        await assert_auth_test_count_async(self, 1)
Beispiel #15
0
    async def test_submission_failure_2(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        request = self.build_valid_request(suggestion_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 1

        app.action({
            "type": "dialog_submission",
            "callback_id": "dialog-callback-iddddd"
        })(handle_submission)
        response = await app.async_dispatch(request)
        assert response.status == 404
        assert self.mock_received_requests["/auth.test"] == 2
Beispiel #16
0
    async def test_simultaneous_requests(self):
        app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret,)
        app.event("app_mention")(random_sleeper)

        request = self.build_valid_app_mention_request()

        times = 10
        tasks = []
        for i in range(times):
            tasks.append(asyncio.ensure_future(app.async_dispatch(request)))

        await asyncio.sleep(5)
        # Verifies all the tasks have been completed with 200 OK
        assert sum([t.result().status for t in tasks if t.done()]) == 200 * times

        assert self.mock_received_requests["/auth.test"] == times
        assert self.mock_received_requests["/chat.postMessage"] == times
Beispiel #17
0
    async def test_save(self):
        timestamp, body = str(int(time())), f"payload={quote(json.dumps(save_payload))}"
        headers = {
            "content-type": ["application/x-www-form-urlencoded"],
            "x-slack-signature": [self.generate_signature(body, timestamp)],
            "x-slack-request-timestamp": [timestamp],
        }
        request = AsyncBoltRequest(body=body, headers=headers)
        response = await self.app.async_dispatch(request)
        assert response.status == 200
        assert self.mock_received_requests["/auth.test"] == 1

        self.app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret)
        self.app.step(
            callback_id="copy_review___", edit=edit, save=save, execute=execute
        )
        response = await self.app.async_dispatch(request)
        assert response.status == 404
Beispiel #18
0
    def __init__(self, data: dict, slack: 'Slack'):
        self.slack = slack
        self.bot = self.slack.bot

        self.team_id = data['team_id']
        self.token = data['token']
        self.bot_id = data['bot_id']
        self.name = self.team_id

        self.app = AsyncApp(token=self.token)

        self.app.view("socket_modal_submission")(self.submission)
        self.app.event("message")(self.slack_message)
        self.app.event("member_joined_channel")(self.slack_member_joined)
        self.app.event("channel_left")(self.slack_channel_left)

        self.handler = AsyncSocketModeHandler(self.app, config.SLACK_APP_TOKEN)
        self.bot.loop.create_task(self.handler.start_async())

        self.bot.add_listener(self.on_message, 'on_message')
        self.bot.add_listener(self.on_raw_message_edit, 'on_raw_message_edit')
        self.bot.add_listener(self.on_raw_message_delete,
                              'on_raw_message_delete')

        self.channels: list[SlackChannel] = []
        self.members: list[SlackMember] = []

        self.slack.bot.loop.create_task(self.get_team_info())

        self.discord_messages: TTLCache[int,
                                        DiscordMessage] = TTLCache(ttl=600.0,
                                                                   maxsize=500)
        self.slack_messages: TTLCache[str,
                                      SlackMessage] = TTLCache(ttl=600.0,
                                                               maxsize=500)
        self.message_links = TTLCache(ttl=86400.0, maxsize=1000)

        self.initialize_data()
        self.messages_cached = asyncio.Event()
        self.members_cached = asyncio.Event()
        self.channels_cached = asyncio.Event()
        self.slack.bot.loop.create_task(self.cache_members())
        self.slack.bot.loop.create_task(self.cache_channels())
        self.slack.bot.loop.create_task(self.cache_messages())
Beispiel #19
0
    async def test_app_home_opened(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
            installation_store=OrgAppInstallationStore(),
        )

        event_payload = {
            "token":
            "verification-token",
            "team_id":
            "T111",
            "enterprise_id":
            "E111",
            "api_app_id":
            "A111",
            "event": {
                "type": "app_home_opened",
                "user": "******",
                "channel": "D111",
                "tab": "messages",
                "event_ts": "1606810927.510671",
            },
            "type":
            "event_callback",
            "event_id":
            "Ev111",
            "event_time":
            1606810927,
            "authorizations": [{
                "enterprise_id": "E111",
                "team_id": None,
                "user_id": "W111",
                "is_bot": True,
                "is_enterprise_install": True,
            }],
            "is_ext_shared_channel":
            False,
        }

        result = Result()

        @app.event("app_home_opened")
        async def handle_app_mention(body):
            assert body == event_payload
            result.called = True

        timestamp, body = str(int(time())), json.dumps(event_payload)
        request: AsyncBoltRequest = AsyncBoltRequest(
            body=body, headers=self.build_headers(timestamp, body))
        response = await app.async_dispatch(request)
        assert response.status == 200
        # auth.test API call must be skipped
        assert self.mock_received_requests["/auth.test"] == 1
        await asyncio.sleep(1)  # wait a bit after auto ack()
        assert result.called is True
Beispiel #20
0
 def test_instance(self):
     server = AsyncSlackAppServer(
         port=3001,
         path="/slack/events",
         app=AsyncApp(
             signing_secret="valid",
             token="xoxb-valid",
         ),
     )
     assert server is not None
Beispiel #21
0
 async def test_no_installation_store(self):
     self.web_client.token = valid_token
     app = AsyncApp(
         client=self.web_client,
         signing_secret=self.signing_secret,
     )
     with pytest.raises(BoltError):
         app.default_tokens_revoked_event_listener()
     with pytest.raises(BoltError):
         app.default_app_uninstalled_event_listener()
     with pytest.raises(BoltError):
         app.enable_token_revocation_listeners()
 async def test_instantiation_non_async_settings_to_app(self):
     with pytest.raises(BoltError):
         AsyncApp(
             signing_secret="xxx",
             oauth_settings=OAuthSettings(
                 client_id="111.222",
                 client_secret="xxx",
                 scopes="chat:write,commands",
             ),
         )
    async def test_disabled(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
            request_verification_enabled=False,
        )
        app.event("app_mention")(whats_up)

        # request including invalid headers
        expired = int(time()) - 3600
        timestamp, body = str(expired), json.dumps(app_mention_body)
        request = AsyncBoltRequest(
            body=body, headers=self.build_headers(timestamp, body)
        )
        response = await app.async_dispatch(request)
        assert response.status == 200
        await assert_auth_test_count_async(self, 1)
        await asyncio.sleep(1)  # wait a bit after auto ack()
        assert self.mock_received_requests["/chat.postMessage"] == 1
    async def test_commands(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )

        async def command_handler(ack):
            await ack()

        app.command("/hello-world")(command_handler)

        input = (
            "token=verification_token"
            "&team_id=T111"
            "&team_domain=test-domain"
            "&channel_id=C111"
            "&channel_name=random"
            "&user_id=W111"
            "&user_name=primary-owner"
            "&command=%2Fhello-world"
            "&text=Hi"
            "&enterprise_id=E111"
            "&enterprise_name=Org+Name"
            "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx"
            "&trigger_id=111.111.xxx"
        )
        timestamp, body = str(int(time())), input

        api = Sanic(name=self.unique_sanic_app_name())
        app_handler = AsyncSlackRequestHandler(app)

        @api.post("/slack/events")
        async def endpoint(req: Request):
            return await app_handler.handle(req)

        _, response = await api.asgi_client.post(
            url="/slack/events",
            data=body,
            headers=self.build_headers(timestamp, body),
        )
        assert response.status_code == 200
        assert_auth_test_count(self, 1)
 async def test_disabled(self):
     app = AsyncApp(
         client=self.web_client,
         signing_secret=self.signing_secret,
         url_verification_enabled=False,
     )
     request = self.build_valid_request()
     response = await app.async_dispatch(request)
     assert response.status == 404
     assert response.body == """{"error": "unhandled request"}"""
     await assert_auth_test_count_async(self, 0)
Beispiel #26
0
    async def test_execute(self):
        timestamp, body = str(int(time())), json.dumps(execute_payload)
        headers = {
            "content-type": ["application/json"],
            "x-slack-signature": [self.generate_signature(body, timestamp)],
            "x-slack-request-timestamp": [timestamp],
        }
        request = AsyncBoltRequest(body=body, headers=headers)
        response = await self.app.async_dispatch(request)
        assert response.status == 200
        assert self.mock_received_requests["/auth.test"] == 1
        await asyncio.sleep(0.5)
        assert self.mock_received_requests["/workflows.stepCompleted"] == 1

        self.app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret)
        self.app.step(
            callback_id="copy_review___", edit=edit, save=save, execute=execute
        )
        response = await self.app.async_dispatch(request)
        assert response.status == 404
 async def test_instance_methods_uncommon_name(self):
     app = AsyncApp(client=self.web_client,
                    signing_secret=self.signing_secret)
     awesome = AwesomeClass("Slackbot")
     app.use(awesome.instance_middleware)
     app.shortcut("test-shortcut")(awesome.instance_method2)
     await self.run_app_and_verify(app)
Beispiel #28
0
    async def test_success_without_type(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
        )
        app.options("dialog-callback-id")(handle_suggestion)
        app.action("dialog-callback-id")(handle_submission_or_cancellation)

        request = self.build_valid_request(suggestion_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 200
        assert response.body != ""
        assert response.headers["content-type"][
            0] == "application/json;charset=utf-8"
        await assert_auth_test_count_async(self, 1)

        request = self.build_valid_request(submission_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 200
        assert response.body == ""
        await assert_auth_test_count_async(self, 1)

        request = self.build_valid_request(cancellation_raw_body)
        response = await app.async_dispatch(request)
        assert response.status == 200
        assert response.body == ""
        await assert_auth_test_count_async(self, 1)
 async def test_default(self):
     app = AsyncApp(
         client=self.web_client,
         signing_secret=self.signing_secret,
     )
     request = self.build_valid_request()
     response = await app.async_dispatch(request)
     assert response.status == 200
     assert (
         response.body ==
         """{"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}"""
     )
     await assert_auth_test_count_async(self, 0)
Beispiel #30
0
    async def test_tokens_revoked(self):
        app = AsyncApp(
            client=self.web_client,
            signing_secret=self.signing_secret,
            installation_store=MyInstallationStore(),
        )

        event_payload = {
            "token": "verification-token",
            "enterprise_id": "E111",
            "api_app_id": "A111",
            "event": {
                "type": "tokens_revoked",
                "tokens": {
                    "oauth": ["W111"],
                    "bot": ["W222"]
                },
            },
            "type": "event_callback",
            "event_id": "Ev111",
            "event_time": 1606805974,
        }

        timestamp, body = str(int(time())), json.dumps(event_payload)
        request: AsyncBoltRequest = AsyncBoltRequest(
            body=body, headers=self.build_headers(timestamp, body))
        response = await app.async_dispatch(request)
        assert response.status == 404

        # Enable the built-in event listeners
        app.enable_token_revocation_listeners()
        response = await app.async_dispatch(request)
        assert response.status == 200
        # auth.test API call must be skipped
        await assert_auth_test_count_async(self, 0)
        await asyncio.sleep(1)  # wait a bit after auto ack()
        assert app.installation_store.delete_bot_called is True
        assert app.installation_store.delete_installation_called is True
        assert app.installation_store.delete_all_called is False