Code Monkey home page Code Monkey logo

tgintegration's Introduction

TgIntegration

An integration test and automation library for Telegram Bots based on Pyrogram.
Test your bot in realtime scenarios!

Are you a user of TgIntegration? I'm actively looking for feedback and ways to improve the library, come and let me know in the official group!

PyPI - Python Version PyPI - Downloads PyPI GitHub top language GitHub Workflow Status (branch) GitHub Workflow Status

FeaturesRequirementsInstallationQuick Start GuideTest Frameworks

Features

▶️ See it in action! 🎬

  • 👤 Log into a Telegram user account and interact with bots or other users
  • ✅ Write realtime integration tests to ensure that your bot works as expected! ▶️ Pytest examples
  • ⚡️ Automate any interaction on Telegram! ▶️ Automatically play @IdleTownBot | More examples
  • 🛡 Fully typed for safety and autocompletion with your favorite IDE
  • 🐍 Built for modern Python (3.8+) with high test coverage

Prerequisites

Same as Pyrogram:

  • A Telegram API key.
  • A user session (seeing things happen in your own account is great for getting started)
  • But: Python 3.8 or higher!

A basic understanding of async/await and asynchronous context managers is assumed, as TgIntegration heavily relies on the latter to automate conversations.

Installation

All hail pip!

$ pip install tgintegration --upgrade


Feeling adventurous?

For bleeding edge, install the master branch:

$ pip install git+https://github.com/JosXa/tgintegration.git

Quick Start Guide

You can follow along by running the example (README)

Setup

Suppose we want to write integration tests for @BotListBot by sending it a couple of messages and checking that it responds the way it should.

After configuring a Pyrogram user client, let's start by creating a BotController:

from tgintegration import BotController

controller = BotController(
    peer="@BotListBot",      # The bot under test is https://t.me/BotListBot 🤖
    client=client,           # This assumes you already have a Pyrogram user client available
    max_wait=8,              # Maximum timeout for responses (optional)
    wait_consecutive=2,      # Minimum time to wait for more/consecutive messages (optional)
    raise_no_response=True,  # Raise `InvalidResponseError` when no response is received (defaults to True)
    global_action_delay=2.5  # Choosing a rather high delay so we can observe what's happening (optional)
)

await controller.clear_chat()  # Start with a blank screen (⚠️)

Now, let's send /start to the bot and wait until exactly three messages have been received by using the asynchronous collect context manager:

async with controller.collect(count=3) as response:
    await controller.send_command("start")

assert response.num_messages == 3  # Three messages received, bundled under a `Response` object
assert response.messages[0].sticker  # The first message is a sticker

The result should look like this:

image

Examining the buttons in the response...

# Get first (and only) inline keyboard from the replies
inline_keyboard = response.inline_keyboards[0]

# Three buttons in the first row
assert len(inline_keyboard.rows[0]) == 3

We can also press the inline keyboard buttons, for example based on a regular expression:

examples = await inline_keyboard.click(pattern=r".*Examples")

As the bot edits the message, .click() automatically listens for "message edited" updates and returns the new state as another Response.

image

assert "Examples for contributing to the BotList" in examples.full_text

Error handling

So what happens when we send an invalid query or the peer fails to respond?

The following instruction will raise an InvalidResponseError after controller.max_wait seconds. This is because we passed raise_no_response=True during controller initialization.

try:
    async with controller.collect():
        await controller.send_command("ayylmao")
except InvalidResponseError:
    pass  # OK

Let's explicitly set raise_ to False so that no exception occurs:

async with controller.collect(raise_=False) as response:
    await client.send_message(controller.peer_id, "Henlo Fren")

In this case, tgintegration will simply emit a warning, but you can still assert that no response has been received by using the is_empty property:

assert response.is_empty

Integrating with Test Frameworks

Pytest is the recommended test framework for use with tgintegration. You can browse through several examples and tgintegration also uses pytest for its own test suite.

unittest

I haven't tried out the builtin unittest library in combination with tgintegration yet, but theoretically I don't see any problems with it. If you do decide to try it, it would be awesome if you could tell me about your experience and whether anything could be improved 🙂 Let us know at 👉 https://t.me/TgIntegration or in an issue.

tgintegration's People

Contributors

dependabot[bot] avatar josxa avatar luchoturtle avatar pietroparini2 avatar pre-commit-ci[bot] avatar spanglelabs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tgintegration's Issues

Consider using tdlight User API

There is now a modified version of the Bot API capable of running in user mode.
It would be a big advantage for integration tests to be able to load off session state handling to an API, immediately solving #18 and making the overall experience a lot easier.
However, Pyrogram is (and won't be) capable of interfacing with (the) REST APIs, so we'd need to think about rewriting or interfacing with aiogram instead.

Pyrogram.api.error doesn't exist

  • tgintegration version: 0.2.4
  • Python version: 3.6.9
  • Operating System: Ubuntu 18.04

Description

What I Did

I've adapted the example for my bot and tried to run it

python3 bot_tester.py

which led to en error:

Traceback (most recent call last):                                                                                                   
  File "bot_tester.py", line 1, in <module>                                                                                          
    from tgintegration import BotController                                                                                          
  File "/usr/local/lib/python3.6/dist-packages/tgintegration/__init__.py", line 3, in <module>                                       
    from tgintegration.botintegrationclient import BotIntegrationClient                                                              
  File "/usr/local/lib/python3.6/dist-packages/tgintegration/botintegrationclient.py", line 9, in <module>                           
    from .interactionclient import InteractionClient                                                                                 
  File "/usr/local/lib/python3.6/dist-packages/tgintegration/interactionclient.py", line 12, in <module>                             
    from pyrogram.api.errors import FloodWait, RpcMcgetFail                                                                          
ModuleNotFoundError: No module named 'pyrogram.api.errors'   

It would seem that in current pyrogram (v 0.17.1) there's no longer a pyrogram.api.error section, and error handling is performed by functions in pyrogram.errors

Sending no-slash commands / plain text

It seems strange to me that adding slash is not an optional parameter. My bot, for example, requires some text responses from the user, and I'd like to mock them, but right now, there's no way I can do it with built-in means.

Anyway, is there a workaround for now?

Pressing the first keyboard button is not possible

  • tgintegration version: 0.2.4
  • Python version: 3.6
  • Operating System: Fedora 28

Description

It is not possible to press the first button of a keyboard by using the index.
The check in keyboard.py:87 does evaluates to false if the index is 0

>>> index=0
>>> pattern=None
>>> any((pattern, index))
False
>>> index=1
>>> any((pattern, index))
True

What I Did

response = inline_keyboard.press_button_await(index=0)
 ValueError: Exactly one of the `pattern` or `index` arguments must be provided.

Visual TestRunner UI inside Telegram

It would be pretty cool to have a visual representation of a running test suite in a regular test message.

I'm imagining something like this:

examples
  pytest
    test_basic
      test_start ✅
      test_ping ✅
      test_help ❌
    test_inlinequeries
      test_simple ✅
      test_a ...
      test_b ...

In any case, there should be an option to view what the agent is doing at the moment, as you don't always have access to the account to check yourself (or it gets too spammy).

Pyrogram==2.0.106 not supported

  • tgintegration version: 1.2.0
  • Python version: 3.10.12
  • Operating System: Ubuntu 22.04

Description

The old version of Pyrogram (1.0) is non functional.
The new 2.0.106 not supported due API changes.

What I Did

the first obvious issue that old methods does not work anymore (like client.send)

can not install tgintegration latest version 0.4.0 with pip

  • Python version: 3.8
  • Operating System: ubuntu 18.04

Description

Currenly, the latest version in pyi.python.org is 0.2.4

Then all example will fail
I trying install by clone your source code and manual install the latest version 0.4.0

Would you please public release of 0.4.0 to pypi ?
Thanks and best regards

keywords mismatch

  • tgintegration version: 0.4.0
  • Python version: 3.6.9
  • Operating System: Ubuntu 18.04

Description

Running the example script doesn't work, because a BotController from the README example expects session_name,api_id and api_hash, but the BotController as in currently is in the source file expects a client of the InteractionClient type and gets surprised by all the other keywords after the bot_under_test.
To the best of my understanding, the idea is to create an InteractionClient, which uses pyrogram's Client, and pass all the keywords to it.
Interestingly, BotIntegrationClient from v 0.2.4 seemingly tries to do that.

Locks for concurrent builds in CI/CD (GitHub Actions)

Generally, mtproto sessions hate being run on the same credentials within multiple connections at the same time. This is especially bad when using string sessions. For CI/CD, there needs to be a solution however to run them in parallel, for example when building in a matrix of multiple Python versions (3.7 + 3.8 in the case of tgintegration) or when multiple pull requests get built at the same time. The only reasonable option for passing Telegram sessions to continuous integration are string sessions, at least to my knowledge.

Import Error

  • tgintegration version: 1.1.0
  • Python version:3.7
  • Operating System:Linux

Description

cannot import name 'BotIntegrationClient' from 'tgintegration'

What I Did

I tried to run below command
"botfather newbot"

Pyrogram 0.7.5 is not supported

  • tgintegration version: 0.2.4
  • Pyrogram 0.7.5
  • Python version: 3.6
  • Operating System: Fedora 28

Description

tgintegration does not work with Pyrogram 0.7.5

Workaround: downgrade Pyrogram 0.7.4

What I Did

I just installed actual tgintegration library via pip and tried the Usage Example

Traceback (most recent call last):
  File "/home/hkoeck/PycharmProjects/resbot/tests/test_demo.py", line 12, in <module>
    client.start()
  File "/home/hkoeck/PycharmProjects/venv/lib64/python3.6/site-packages/tgintegration/botintegrationclient.py", line 62, in start
    res = super().start(debug=debug)
TypeError: start() got an unexpected keyword argument 'debug'

Looks like Pyrogram 0.7.5 does not support the argument debug any more

TgIntegration does not work when using asyncio.run()

In Python 3.8, the examples cannot be run using asyncio.run(...), instead only asyncio.get_event_loop().run_until_complete() works.
It seems as if Pyrogram ran on a different loop when using asyncio.run().

If anyone has more insights or is aware of any known issues please let me know.

Feature request: send message in groupchat

  • tgintegration version: 0.16.0
  • Python version: 3.6.3
  • Operating System: Windows

Description

It would be very handy to be able to test that the bot responds correctly to a message in a groupchat. Currently this library only tests against the bot in privmsg

What I Did

Tried feeding chat_id parameter to send_message_await(), but it does not like that, as it's overridden to the bot id

Examples for unittest module

This should be a simple first issue for someone who has experience with the stdlib unittest module, perfectly suited for Hacktoberfest 🥰

There is already a pytest suite in the examples folder. It would be nice to also include a sample test suite for the builtin Python unittest module for devs who are more familiar with that.

I have no idea how asyncio works in combination with unittest, but that is obviously a requirement.
What I'm expecting would be that you pick any bot (it can even be your own if you're sure it's going to stay online a high percentage of time) and create some class-based and function-based tests for basic bot functionality based on what you know are good testing practices.

[Security] Workflow build.yml is using vulnerable action pre-commit/action

The workflow build.yml is referencing action pre-commit/action using references v2.0.0. However this reference is missing the commit 80db042ff08cdddbbbfb6f89c06a6bfc4dddf0b7 which may contain fix to the some vulnerability.
The vulnerability fix that is missing by actions version could be related to:
(1) CVE fix
(2) upgrade of vulnerable dependency
(3) fix to secret leak and others.
Please consider to update the reference to the action.

Allow adding Pyrogram handlers during execution of a Pyrogram callback

Pyrogram has a problem where it uses an asyncio mechanism to first acquire the locks responsible for safe handler manipulations when calling client.add_handler, but at least one of these locks will be used when Pyrogram executes a callback.
This means that there is categorically a deadlock when attempting to add a handler inside of a handler callback. Dan's way around that is to have an async helper function to acquire all locks and then add the handler, which is executed as fire-and-forget (asynchronously):

https://github.com/pyrogram/pyrogram/blob/332468d271896e2c81b1a283b6a0f14fc1a666c1/pyrogram/dispatcher.py#L137-L152

This however means that client.add_handler will always have effect only after the dispatcher is done executing the current callback, making it unusable for tgintegration. For the 1.0 release, this was not an issue as it's only advertised as a test framework and automation tool and not for userbots, but should be looked into in the future.

_raise=False failure to catch TimeoutError on python 3.7

  • tgintegration version: 1.1.0
  • Python version: 3.7
  • Operating System: Linux

Description

Running a test which is ensuring the bot does not respond, like so:

async with controller.collect(peer=group_id, raise_=False) as response:
    await controller.client.send_photo(group_id, thumb_link
assert response.num_messages == 0

What I Did

It passes fine using python 3.8, but on python 3.7 it fails to catch the TimeoutError.
See this build log:
https://travis-ci.com/github/Deer-Spangle/FA-search-bot/jobs/439782520 for the full traceback

No type inference for Response type in collector when using PyCharm

When using async with controller.collect(...) as response, PyCharm fails to understand the AsyncContextManager[Response] type annotation and infers response as Any.

This is due to a bug in PyCharm itself, which is reported on their YouTrack board (and not planned since two years): https://youtrack.jetbrains.com/issue/PY-29891

If someone wants to look into this, here are the relevant type annotations:

Now, since this is the integral part of tgintegration, we need to find a solution (Update 2020-10-25: # type: Response has been added to all examples):

  1. Leave it as it is, wait for and push JetBrains to fix the bug (I'd need help from the community for that). Add documentation on how to use typing.cast or as response: # type: Response as workarounds.
  2. Keep trying to find another workaround that will make PyCharm happy.
  3. Re-architect the @asynccontextmanager design (please no, I find it great 🤦)

Containers should only work with Clients, not Controllers

It is unnecessary that containers and the standalone collect methods accept a BotController as parameter. If they simply took a Client instead, it would allow for easier integration of the library in places where a full-blown controller object would be overkill.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.