test(e2e): added TelegramClient e2e tests

This commit is contained in:
alina 🌸 2023-12-23 22:00:20 +03:00
parent ca1916c5cb
commit 9ff6a628e5
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
13 changed files with 390 additions and 20 deletions

View file

@ -9,15 +9,20 @@ module.exports = {
},
ts: {
getFiles: () => 'tests/**/*.ts',
beforeAll: () => [
'tsc',
'node build-esm.cjs',
],
runFile: (file) => [
`mocha -r ts-node/register ${file}`,
`mocha dist/${file.replace(/\.ts$/, '.js')}`,
`node run-esm.cjs ${file}`,
`mocha dist/esm/${file.replace(/\.ts$/, '.js')}`,
],
beforeAll: () => ['tsc', 'node build-esm.cjs'],
runFile: (file) => {
if (file.startsWith('tests/packaging/')) {
// packaging tests - we need to make sure everything imports and works
return [
`mocha -r ts-node/register ${file}`,
`mocha dist/${file.replace(/\.ts$/, '.js')}`,
`node run-esm.cjs ${file}`,
`mocha dist/esm/${file.replace(/\.ts$/, '.js')}`,
]
}
// normal e2e tests - testing features etc
return `mocha dist/${file.replace(/\.ts$/, '.js')}`
},
},
}

View file

@ -70,6 +70,7 @@ function runForDir(dir) {
}
const files = glob.sync(getFiles(), { cwd: path.join(__dirname, dir) })
files.sort()
for (const file of files) {
runForFile(dir, file, false)

View file

@ -1,4 +1,3 @@
/* eslint-disable no-restricted-globals */
const fs = require('fs')
const path = require('path')
const cp = require('child_process')

77
e2e/ts/tests/01.auth.ts Normal file
View file

@ -0,0 +1,77 @@
import { expect } from 'chai'
import { describe, it } from 'mocha'
import { MtUnsupportedError, TelegramClient } from '@mtcute/client'
import { getApiParams } from '../utils.js'
const getAccountId = () =>
Math.floor(Math.random() * 10000)
.toString()
.padStart(4, '0')
describe('1. authorization', function () {
this.timeout(300_000)
it('should authorize in default dc', async () => {
const tg = new TelegramClient(getApiParams('dc2.session'))
// reset storage just in case
await tg.storage.load?.()
await tg.storage.reset(true)
while (true) {
const phone = `999662${getAccountId()}`
let user
try {
user = await tg.start({
phone,
code: () => '22222',
})
} catch (e) {
if (e instanceof MtUnsupportedError && e.message.includes('Signup is no longer supported')) {
// retry with another number
continue
} else throw e
}
await tg.close()
expect(user.isSelf).to.be.true
expect(user.phoneNumber).to.equal(phone)
break
}
})
it('should authorize in dc 1', async () => {
const tg = new TelegramClient(getApiParams('dc1.session'))
// reset storage just in case
await tg.storage.load?.()
await tg.storage.reset(true)
while (true) {
const phone = `999661${getAccountId()}`
let user
try {
user = await tg.start({
phone,
code: () => '11111',
})
} catch (e) {
if (e instanceof MtUnsupportedError && e.message.includes('Signup is no longer supported')) {
// retry with another number
continue
} else throw e
}
await tg.close()
expect(user.isSelf).to.be.true
expect(user.phoneNumber).to.equal(phone)
break
}
})
})

View file

@ -0,0 +1,48 @@
import { expect } from 'chai'
import { describe, it } from 'mocha'
import { TelegramClient } from '@mtcute/client'
import { getApiParams } from '../utils.js'
describe('2. calling methods', function () {
this.timeout(300_000)
const tg = new TelegramClient(getApiParams('dc2.session'))
this.beforeAll(() => tg.connect())
this.afterAll(() => tg.close())
it('getUsers(@BotFather)', async () => {
const [user] = await tg.getUsers('botfather')
expect(user?.isBot).to.be.true
expect(user?.displayName).to.equal('BotFather')
})
it('getUsers(@BotFather) - cached', async () => {
const [user] = await tg.getUsers('botfather')
expect(user?.isBot).to.be.true
expect(user?.displayName).to.equal('BotFather')
})
it('getHistory(777000)', async () => {
const history = await tg.getHistory(777000, { limit: 5 })
expect(history[0].chat.chatType).to.equal('private')
expect(history[0].chat.id).to.equal(777000)
expect(history[0].chat.firstName).to.equal('Telegram')
})
it('updateProfile', async () => {
const bio = `mtcute e2e ${new Date().toISOString()}`
const oldSelf = await tg.getFullChat('self')
const res = await tg.updateProfile({ bio })
const newSelf = await tg.getFullChat('self')
expect(res.isSelf).to.be.true
expect(oldSelf.bio).to.not.equal(newSelf.bio)
expect(newSelf.bio).to.equal(bio)
})
})

167
e2e/ts/tests/03.files.ts Normal file
View file

@ -0,0 +1,167 @@
/* eslint-disable no-restricted-imports */
import { expect } from 'chai'
import { createHash } from 'crypto'
import { describe, it } from 'mocha'
import { FileDownloadLocation, TelegramClient, Thumbnail } from '@mtcute/client'
import { sleep } from '@mtcute/core/utils.js'
import { getApiParams } from '../utils.js'
const CINNAMOROLL_PFP_CHAT = 'test_file_dc2'
const CINNAMOROLL_PFP_THUMB_SHA256 = '3e6f220235a12547c16129f50c19ed3224d39b827414d1d500f79569a3431eae'
const CINNAMOROLL_PFP_SHA256 = '4d9836a71ac039f5656cde55b83525871549bfbff9cfb658c3f8381c5ba89ce8'
const UWU_MSG = 'https://t.me/test_file_dc2/8'
const UWU_SHA256 = '357b78c9f9d20e813f729a19dd90c6727f30ebd4c8c83557022285f283a705b9'
const SHREK_MSG = 'https://t.me/test_file_dc2/11'
const SHREK_SHA256 = 'd3e6434e027f3d31dc3e05c6ea2eaf84fdd1fb00774a215f89d9ed8b56f86258'
const LARGE_MSG = 'https://t.me/test_file_dc2/12'
async function downloadAsSha256(client: TelegramClient, location: FileDownloadLocation): Promise<string> {
const sha = createHash('sha256')
for await (const chunk of client.downloadAsIterable(location)) {
sha.update(chunk)
}
return sha.digest('hex')
}
describe('3. working with files', function () {
this.timeout(300_000)
// sometimes test dcs are overloaded and we get FILE_REFERENCE_EXPIRED
// because we got multiple -500:No workers running errors in a row
// we currently don't have file references database, so we can just retry the test for now
this.retries(2)
describe('same-dc', () => {
const tg = new TelegramClient(getApiParams('dc2.session'))
this.beforeAll(() => tg.connect())
this.afterAll(() => tg.close())
it('should download pfp thumbs', async () => {
const chat = await tg.getChat(CINNAMOROLL_PFP_CHAT)
if (!chat.photo) expect.fail('Chat has no photo')
expect(await downloadAsSha256(tg, chat.photo.big)).to.equal(CINNAMOROLL_PFP_THUMB_SHA256)
})
it('should download animated pfps', async () => {
const chat = await tg.getFullChat(CINNAMOROLL_PFP_CHAT)
const thumb = chat.fullPhoto?.getThumbnail(Thumbnail.THUMB_VIDEO_PROFILE)
if (!thumb) expect.fail('Chat has no animated pfp')
expect(await downloadAsSha256(tg, thumb)).to.equal(CINNAMOROLL_PFP_SHA256)
})
it('should download photos', async () => {
const msg = await tg.getMessageByLink(UWU_MSG)
if (msg?.media?.type !== 'photo') {
expect.fail('Message not found or not a photo')
}
expect(await downloadAsSha256(tg, msg.media)).to.equal(UWU_SHA256)
})
it('should download documents', async () => {
const msg = await tg.getMessageByLink(SHREK_MSG)
if (msg?.media?.type !== 'document') {
expect.fail('Message not found or not a document')
}
expect(await downloadAsSha256(tg, msg.media)).to.equal(SHREK_SHA256)
})
it('should cancel downloads', async () => {
const msg = await tg.getMessageByLink(LARGE_MSG)
if (msg?.media?.type !== 'document') {
expect.fail('Message not found or not a document')
}
const media = msg.media
const abort = new AbortController()
let downloaded = 0
async function download() {
const dl = tg.downloadAsIterable(media, { abortSignal: abort.signal })
try {
for await (const chunk of dl) {
downloaded += chunk.length
}
} catch (e) {
if (!(e instanceof DOMException && e.name === 'AbortError')) throw e
}
}
const promise = download()
// let it download for 10 seconds
await sleep(10000)
abort.abort()
// abort and snap the downloaded amount
const downloadedBefore = downloaded
const avgSpeed = downloaded / 10
// eslint-disable-next-line no-console
console.log('Average speed: %d KiB/s', avgSpeed / 1024)
// wait a bit more to make sure it's aborted
await sleep(2000)
await promise
expect(downloaded).to.equal(downloadedBefore, 'nothing should be downloaded after abort')
})
})
describe('cross-dc', () => {
const tg = new TelegramClient(getApiParams('dc1.session'))
this.beforeAll(() => tg.connect())
this.afterAll(() => tg.close())
it('should download pfp thumbs', async () => {
const chat = await tg.getChat(CINNAMOROLL_PFP_CHAT)
if (!chat.photo) expect.fail('Chat has no photo')
expect(await downloadAsSha256(tg, chat.photo.big)).to.equal(CINNAMOROLL_PFP_THUMB_SHA256)
})
it('should download animated pfps', async () => {
const chat = await tg.getFullChat(CINNAMOROLL_PFP_CHAT)
const thumb = chat.fullPhoto?.getThumbnail(Thumbnail.THUMB_VIDEO_PROFILE)
if (!thumb) expect.fail('Chat has no animated pfp')
expect(await downloadAsSha256(tg, thumb)).to.equal(CINNAMOROLL_PFP_SHA256)
})
it('should download photos', async () => {
const msg = await tg.getMessageByLink(UWU_MSG)
if (msg?.media?.type !== 'photo') {
expect.fail('Message not found or not a photo')
}
expect(await downloadAsSha256(tg, msg.media)).to.equal(UWU_SHA256)
})
it('should download documents', async () => {
const msg = await tg.getMessageByLink(SHREK_MSG)
if (msg?.media?.type !== 'document') {
expect.fail('Message not found or not a document')
}
expect(await downloadAsSha256(tg, msg.media)).to.equal(SHREK_SHA256)
})
})
})

View file

@ -0,0 +1,49 @@
import { expect } from 'chai'
import { describe, it } from 'mocha'
import { Message, TelegramClient } from '@mtcute/client'
import { getApiParams, waitFor } from '../utils.js'
describe('4. handling updates', async function () {
this.timeout(300_000)
const tg1 = new TelegramClient(getApiParams('dc1.session'))
tg1.log.prefix = '[tg1] '
const tg2 = new TelegramClient(getApiParams('dc2.session'))
tg2.log.prefix = '[tg2] '
this.beforeAll(async () => {
await tg1.connect()
await tg1.startUpdatesLoop()
await tg2.connect()
})
this.afterAll(async () => {
await tg1.close()
await tg2.close()
})
it('should send and receive messages', async () => {
const tg1Messages: Message[] = []
tg1.on('new_message', (msg) => tg1Messages.push(msg))
const [tg1User] = await tg1.getUsers('self')
let username = tg1User!.username
if (!username) {
username = `mtcute_e2e_${Math.random().toString(36).slice(2)}`
await tg1.setMyUsername(username)
}
const messageText = `mtcute test message ${Math.random().toString(36).slice(2)}`
const sentMsg = await tg2.sendText(username, messageText)
expect(sentMsg.text).to.equal(messageText)
expect(sentMsg.chat.id).to.equal(tg1User!.id)
await waitFor(() => {
expect(tg1Messages.find((msg) => msg.text === messageText)).to.exist
})
})
})

View file

@ -4,7 +4,7 @@ import { describe, it } from 'mocha'
import { BaseTelegramClient } from '@mtcute/core'
// @fix-import
import { getApiParams } from '../utils'
import { getApiParams } from '../../utils'
describe('@mtcute/core', function () {
this.timeout(300_000)

View file

@ -81,7 +81,7 @@ describe('TlBinaryWriter', () => {
w.bytes(obj.pq)
w.vector(w.long, obj.serverPublicKeyFingerprints)
},
_staticSize: {} as any
_staticSize: {} as any,
}
it('should work with Buffers', () => {

View file

@ -1,8 +1,12 @@
import { BaseTelegramClientOptions } from '@mtcute/core'
import { MemoryStorage } from '@mtcute/core/storage/memory.js'
import { LogManager } from '@mtcute/core/utils.js'
// eslint-disable-next-line no-restricted-imports
import { join } from 'path'
export const getApiParams = (): BaseTelegramClientOptions => {
import { BaseTelegramClientOptions, MaybeAsync } from '@mtcute/core'
import { MemoryStorage } from '@mtcute/core/storage/memory.js'
import { LogManager, sleep } from '@mtcute/core/utils.js'
import { SqliteStorage } from '@mtcute/sqlite'
export const getApiParams = (storage?: string): BaseTelegramClientOptions => {
if (!process.env.API_ID || !process.env.API_HASH) {
throw new Error('API_ID and API_HASH env variables must be set')
}
@ -11,7 +15,25 @@ export const getApiParams = (): BaseTelegramClientOptions => {
apiId: parseInt(process.env.API_ID),
apiHash: process.env.API_HASH,
testMode: true,
storage: new MemoryStorage(),
logLevel: LogManager.DEBUG,
storage: storage ? new SqliteStorage(join(__dirname, storage)) : new MemoryStorage(),
logLevel: LogManager.VERBOSE,
}
}
export async function waitFor(condition: () => MaybeAsync<void>, timeout = 5000): Promise<void> {
const start = Date.now()
let lastError
while (Date.now() - start < timeout) {
try {
await condition()
return
} catch (e) {
lastError = e
await sleep(100)
}
}
throw lastError
}

View file

@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest'
import { Long, toggleChannelIdMark } from '@mtcute/core'
import { createStub, StubTelegramClient } from '@mtcute/test'
import { getAuthState } from '../auth/_state.js'
import { getAuthState, setupAuthState } from '../auth/_state.js'
import { sendText } from './send-text.js'
const stubUser = createStub('user', {
@ -104,6 +104,7 @@ describe('sendText', () => {
const client = new StubTelegramClient()
await client.registerPeers(stubUser)
setupAuthState(client)
getAuthState(client).userId = stubUser.id
client.respondWith('messages.sendMessage', () =>
@ -127,6 +128,7 @@ describe('sendText', () => {
it('should correctly handle updateShortSentMessage without cached peer', async () => {
const client = new StubTelegramClient()
setupAuthState(client)
getAuthState(client).userId = stubUser.id
const getUsersFn = client.respondWith(