test(e2e): massive rework of e2e tests
now they run as-is, without any bundling. this might seem like a downgrade, but we no longer really need to verify that we publish stuff correctly as we delegate that to `@fuman/build` meow
This commit is contained in:
parent
f0451d56e3
commit
af54f6e1c3
99 changed files with 1147 additions and 2647 deletions
|
@ -1,7 +1,6 @@
|
|||
import * as vitestExpect from '@vitest/expect'
|
||||
import * as vitestSpy from '@vitest/spy'
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, vi as bunVi, it, jest } from 'bun:test'
|
||||
// https://github.com/oven-sh/bun/issues/6044
|
||||
import * as chai from 'chai'
|
||||
|
||||
import { setupChai, stubGlobal, unstubAllGlobals, waitFor } from './polyfills'
|
||||
|
|
|
@ -55,7 +55,7 @@ export default defineConfig({
|
|||
name: 'polyfills',
|
||||
transform(code) {
|
||||
if (!code.includes('vitest')) return code
|
||||
code = code.replace(/^import \{(.+?)\} from ['"]vitest['"]/gms, (_, names) => {
|
||||
code = code.replace(/^import \{([^}]+)\} from ['"]vitest['"];?$/gm, (_, names) => {
|
||||
const namesParsed = names.split(',').map(name => name.trim())
|
||||
|
||||
const namesFromFixup: string[] = []
|
||||
|
|
|
@ -54,7 +54,7 @@ export default defineConfig({
|
|||
name: 'polyfills',
|
||||
transform(code) {
|
||||
if (!code.includes('vitest')) return code
|
||||
code = code.replace(/^import \{(.+?)\} from ['"]vitest['"]/gms, (_, names) => {
|
||||
code = code.replace(/^import \{([^}]+)\} from ['"]vitest['"];?$/gm, (_, names) => {
|
||||
const namesParsed = names.split(',').map(name => name.trim())
|
||||
|
||||
return `import {${namesParsed.join(', ')}} from '${POLYFILLS}'`
|
||||
|
|
22
.github/workflows/test.yaml
vendored
22
.github/workflows/test.yaml
vendored
|
@ -69,11 +69,11 @@ jobs:
|
|||
- uses: ./.github/actions/init
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: '1.46.3'
|
||||
deno-version: '2.0'
|
||||
- name: 'Build tests'
|
||||
run: pnpm exec vite build -c .config/vite.deno.ts
|
||||
- name: 'Run tests'
|
||||
run: cd dist/tests && deno test -A --unstable-ffi
|
||||
run: cd dist/tests && deno test -A --unstable-ffi --node-modules-dir=false
|
||||
|
||||
test-web:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -102,12 +102,15 @@ jobs:
|
|||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/init
|
||||
- name: Run end-to-end tests
|
||||
env:
|
||||
API_ID: ${{ secrets.TELEGRAM_API_ID }}
|
||||
API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
|
||||
SESSION_DC1: ${{ secrets.SESSION_DC1 }}
|
||||
SESSION_DC2: ${{ secrets.SESSION_DC2 }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: cd e2e/node && ./cli.sh ci
|
||||
run: 'cd e2e && pnpm run test:all'
|
||||
e2e-deno:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test-node, test-web, test-bun, test-deno]
|
||||
|
@ -116,17 +119,18 @@ jobs:
|
|||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/init
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: '2.0'
|
||||
- name: Run end-to-end tests under Deno
|
||||
env:
|
||||
API_ID: ${{ secrets.TELEGRAM_API_ID }}
|
||||
API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
|
||||
SESSION_DC1: ${{ secrets.SESSION_DC1 }}
|
||||
SESSION_DC2: ${{ secrets.SESSION_DC2 }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-fields/retry@v2
|
||||
# thanks docker networking very cool
|
||||
with:
|
||||
max_attempts: 3
|
||||
timeout_minutes: 30
|
||||
command: cd e2e/deno && ./cli.sh ci
|
||||
run: 'cd e2e && pnpm run deno:test:all'
|
||||
|
||||
cr:
|
||||
needs:
|
||||
|
|
9
e2e/.env.example
Normal file
9
e2e/.env.example
Normal file
|
@ -0,0 +1,9 @@
|
|||
# obtain these values from my.telegram.org
|
||||
API_ID=
|
||||
API_HASH=
|
||||
|
||||
# mtcute session strings for test dc1 and test dc2
|
||||
SESSION_DC1=
|
||||
SESSION_DC2=
|
||||
|
||||
GITHUB_TOKEN=
|
1
e2e/.gitignore
vendored
Normal file
1
e2e/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.env
|
2
e2e/_runtime/.gitignore
vendored
Normal file
2
e2e/_runtime/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
50
e2e/deno.lock
Normal file
50
e2e/deno.lock
Normal file
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@fuman/utils@0.0.1": "0.0.1",
|
||||
"jsr:@std/assert@^1.0.8": "1.0.8",
|
||||
"jsr:@std/internal@^1.0.5": "1.0.5",
|
||||
"jsr:@std/testing@1.0.5": "1.0.5"
|
||||
},
|
||||
"jsr": {
|
||||
"@fuman/utils@0.0.1": {
|
||||
"integrity": "7cf43898814272c0918e813b34a1ae848ded5710383018dacd27a8fbb1dd6437"
|
||||
},
|
||||
"@std/assert@1.0.8": {
|
||||
"integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b"
|
||||
},
|
||||
"@std/internal@1.0.5": {
|
||||
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
|
||||
},
|
||||
"@std/testing@1.0.5": {
|
||||
"integrity": "6e693cbec94c81a1ad3df668685c7ba8e20742bb10305bc7137faa5cf16d2ec4",
|
||||
"dependencies": [
|
||||
"jsr:@std/assert",
|
||||
"jsr:@std/internal"
|
||||
]
|
||||
}
|
||||
},
|
||||
"redirects": {
|
||||
"https://esm.sh/v135/@types/chai@~5/index.d.ts": "https://esm.sh/v135/@types/chai@5.0.1/index.d.ts"
|
||||
},
|
||||
"remote": {
|
||||
"https://esm.sh/chai@5.1.2": "52c79876382aaf6855c55ea66e2ff88675457f4727bfde37363bb199cdda8488",
|
||||
"https://esm.sh/v135/chai@5.1.2/denonext/chai.mjs": "05cc6071c804cf39d4325a2c93807727623019abedca6ed1cb6534137bd4f65e"
|
||||
},
|
||||
"workspace": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@fuman/utils@0.0.1",
|
||||
"npm:@types/chai@^4.3.8",
|
||||
"npm:@types/mocha@^10.0.2",
|
||||
"npm:@types/node@^20.8.10",
|
||||
"npm:better-sqlite3@11.6.0",
|
||||
"npm:chai@^4.3.10",
|
||||
"npm:dotenv-cli@7.4.4",
|
||||
"npm:esbuild@0.24",
|
||||
"npm:globstar@1.0.0",
|
||||
"npm:tsx@^4.19.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
/.jsr-data
|
||||
/dist
|
||||
/deno.lock
|
|
@ -1,5 +0,0 @@
|
|||
# obtain these values from my.telegram.org
|
||||
API_ID=
|
||||
API_HASH=
|
||||
|
||||
GITHUB_TOKEN=
|
4
e2e/deno/.gitignore
vendored
4
e2e/deno/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
/.jsr-data
|
||||
.env
|
||||
/deno.lock
|
||||
/.sessions
|
|
@ -1,24 +0,0 @@
|
|||
FROM denoland/deno:bin-1.45.5 as deno-bin
|
||||
|
||||
FROM node:20
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=deno-bin /deno /bin/deno
|
||||
|
||||
RUN corepack enable && \
|
||||
corepack prepare pnpm@9.0.6 --activate
|
||||
|
||||
COPY ../.. /app/
|
||||
|
||||
RUN pnpm install --frozen-lockfile && \
|
||||
pnpm -C packages/tl run gen-code
|
||||
|
||||
RUN apt update && apt install -y socat
|
||||
|
||||
ENV REGISTRY="http://jsr/"
|
||||
ENV E2E="1"
|
||||
ENV JSR="1"
|
||||
ENV JSR_TOKEN="token"
|
||||
|
||||
ENTRYPOINT [ "node", "/app/scripts/publish.js" ]
|
||||
CMD [ "all" ]
|
|
@ -1,3 +0,0 @@
|
|||
FROM ghcr.io/teidesu/jsr-api:latest
|
||||
|
||||
RUN apt update && apt install -y curl
|
|
@ -1,10 +0,0 @@
|
|||
FROM denoland/deno:1.45.5
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt update && apt install -y socat
|
||||
|
||||
COPY ./ /app/
|
||||
|
||||
ENV DOCKER="1"
|
||||
|
||||
ENTRYPOINT [ "./cli.sh", "run" ]
|
|
@ -1,30 +0,0 @@
|
|||
# mtcute e2e tests (Deno edition)
|
||||
|
||||
This directory contains end-to-end tests for mtcute under Deno.
|
||||
|
||||
They are made for 2 purposes:
|
||||
- Ensure published packages work as expected and can properly be imported
|
||||
- Ensure that the library works with the actual Telegram API
|
||||
|
||||
To achieve the first goal, we use a local JSR instance container where we publish the package,
|
||||
and then install it from there in another container
|
||||
|
||||
## Setting up
|
||||
|
||||
Before running the tests, you need to copy `.env.example` to `.env` and fill in the values
|
||||
|
||||
## Running tests
|
||||
|
||||
```bash
|
||||
# first start a local jsr instance
|
||||
./cli.sh start
|
||||
|
||||
# push all packages to the local registry
|
||||
./cli.sh update
|
||||
# pushing a particular package is not supported due to jsr limitations
|
||||
|
||||
# run the tests
|
||||
./cli.sh run
|
||||
# or in docker
|
||||
./cli.sh run-docker
|
||||
```
|
|
@ -1,90 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eau
|
||||
|
||||
method=$1
|
||||
shift
|
||||
|
||||
case "$method" in
|
||||
"start")
|
||||
docker compose up -d --wait jsr
|
||||
node ./init-server.js
|
||||
;;
|
||||
"update")
|
||||
# unpublish all packages
|
||||
if [ -d .jsr-data/gcs/modules/@mtcute ]; then
|
||||
rm -rf .jsr-data/gcs/modules/@mtcute
|
||||
docker compose exec jsr-db psql registry -U user -c "delete from publishing_tasks;"
|
||||
docker compose exec jsr-db psql registry -U user -c "delete from package_files;"
|
||||
docker compose exec jsr-db psql registry -U user -c "delete from npm_tarballs;"
|
||||
docker compose exec jsr-db psql registry -U user -c "delete from package_version_dependencies;"
|
||||
docker compose exec jsr-db psql registry -U user -c "delete from package_versions;"
|
||||
docker compose exec jsr-db psql registry -U user -c "delete from packages;"
|
||||
fi
|
||||
|
||||
# publish all packages
|
||||
docker compose run --rm --build build all
|
||||
|
||||
# clear cache
|
||||
if command -v deno &> /dev/null; then
|
||||
rm -rf $(deno info --json | jq .denoDir -r)/deps
|
||||
fi
|
||||
if [ -f deno.lock ]; then
|
||||
rm deno.lock
|
||||
fi
|
||||
;;
|
||||
"clean")
|
||||
docker compose down
|
||||
rm -rf .jsr-data
|
||||
;;
|
||||
"stop")
|
||||
docker compose down
|
||||
;;
|
||||
"run")
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
fi
|
||||
|
||||
export JSR_URL=http://localhost:4873
|
||||
|
||||
if [ ! -z ${DOCKER+x} ]; then
|
||||
# running behind a socat proxy seems to fix some of the docker networking issues (thx kamillaova)
|
||||
socat TCP-LISTEN:4873,fork,reuseaddr TCP4:jsr:80 &
|
||||
socat_pid=$!
|
||||
|
||||
# run `deno cache` with a few retries to make sure everything is cached
|
||||
for i in {1..5}; do
|
||||
if deno cache tests/*.ts; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
trap "kill $socat_pid" EXIT
|
||||
fi
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
deno test -A --unstable-ffi tests/**/*.ts
|
||||
else
|
||||
deno test -A --unstable-ffi $@
|
||||
fi
|
||||
;;
|
||||
"run-docker")
|
||||
source .env
|
||||
docker compose run --rm --build test $@
|
||||
;;
|
||||
"ci")
|
||||
set -eaux
|
||||
if [ -d .jsr-data ]; then
|
||||
# clean up data from previous runs
|
||||
docker compose down
|
||||
sudo rm -rf .jsr-data
|
||||
fi
|
||||
mkdir .jsr-data
|
||||
./cli.sh start
|
||||
./cli.sh update
|
||||
docker compose run --rm --build test
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command"
|
||||
;;
|
||||
esac
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"imports": {
|
||||
"@mtcute/web": "jsr:@mtcute/web@*",
|
||||
"@mtcute/wasm": "jsr:@mtcute/wasm@*",
|
||||
"@mtcute/tl": "jsr:@mtcute/tl@*",
|
||||
"@mtcute/tl-runtime": "jsr:@mtcute/tl-runtime@*",
|
||||
"@mtcute/core": "jsr:@mtcute/core@*",
|
||||
"@mtcute/deno": "jsr:@mtcute/deno@*"
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
version: "3"
|
||||
services:
|
||||
# jsr (based on https://github.com/teidesu/docker-images/blob/main/jsr/docker-compose.yaml)
|
||||
jsr-db:
|
||||
image: postgres:15
|
||||
command: postgres -c 'max_connections=1000'
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: registry
|
||||
healthcheck:
|
||||
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
|
||||
interval: 5s
|
||||
retries: 20
|
||||
start_period: 5s
|
||||
volumes:
|
||||
- ./.jsr-data/db:/var/lib/postgresql/data
|
||||
jsr-gcs:
|
||||
image: fsouza/fake-gcs-server:latest
|
||||
command: -scheme http -filesystem-root=/gcs-data -port 4080
|
||||
volumes:
|
||||
- ./.jsr-data/gcs:/gcs-data
|
||||
jsr-api:
|
||||
depends_on:
|
||||
jsr-db:
|
||||
condition: service_healthy
|
||||
jsr-gcs:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test: "curl --fail http://localhost:8001/sitemap.xml || exit 1"
|
||||
interval: 5s
|
||||
retries: 20
|
||||
start_period: 5s
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.jsr
|
||||
environment:
|
||||
- "DATABASE_URL=postgres://user:password@jsr-db/registry"
|
||||
- "GITHUB_CLIENT_ID=fake"
|
||||
- "GITHUB_CLIENT_SECRET=fake"
|
||||
- "GCS_ENDPOINT=http://jsr-gcs:4080"
|
||||
- "MODULES_BUCKET=modules"
|
||||
- "PUBLISHING_BUCKET=publishing"
|
||||
- "DOCS_BUCKET=docs"
|
||||
- "NPM_BUCKET=npm"
|
||||
- "REGISTRY_URL=http://localhost:4873"
|
||||
- "NPM_URL=http://example.com/unused"
|
||||
jsr:
|
||||
depends_on:
|
||||
jsr-api:
|
||||
condition: service_healthy
|
||||
image: nginx:1.21
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
ports:
|
||||
- "4873:80"
|
||||
|
||||
# our stuff
|
||||
build:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: e2e/deno/Dockerfile.build
|
||||
environment:
|
||||
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||
depends_on:
|
||||
- jsr
|
||||
test:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.test
|
||||
environment:
|
||||
- API_ID=${API_ID}
|
||||
- API_HASH=${API_HASH}
|
||||
depends_on:
|
||||
- jsr
|
||||
networks:
|
||||
mtcute-e2e: {}
|
|
@ -1,65 +0,0 @@
|
|||
/* eslint-disable no-console */
|
||||
import { execSync } from 'node:child_process'
|
||||
|
||||
function getDockerContainerIp(name) {
|
||||
const containerId = execSync(`docker compose ps -q ${name}`).toString().trim()
|
||||
const ip = execSync(`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${containerId}`)
|
||||
.toString()
|
||||
.trim()
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
for (const stmt of [
|
||||
"delete from tokens where user_id = '00000000-0000-0000-0000-000000000000';",
|
||||
"insert into tokens (hash, user_id, type, expires_at) values ('3c469e9d6c5875d37a43f353d4f88e61fcf812c66eee3457465a40b0da4153e0', '00000000-0000-0000-0000-000000000000', 'web', current_date + interval '100' year);",
|
||||
"update users set is_staff = true, scope_limit = 99999 where id = '00000000-0000-0000-0000-000000000000';",
|
||||
]) {
|
||||
execSync(`docker compose exec jsr-db psql registry -U user -c "${stmt}"`)
|
||||
}
|
||||
|
||||
console.log('[i] Initialized database')
|
||||
|
||||
const GCS_URL = `http://${getDockerContainerIp('jsr-gcs')}:4080/`
|
||||
const API_URL = `http://${getDockerContainerIp('jsr-api')}:8001/`
|
||||
|
||||
async function createBucket(name) {
|
||||
try {
|
||||
const resp = await fetch(`${GCS_URL}storage/v1/b`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name }),
|
||||
})
|
||||
|
||||
await resp.text()
|
||||
|
||||
return resp.ok || resp.status === 409
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (const bucket of ['modules', 'docs', 'publishing', 'npm']) {
|
||||
const ok = await createBucket(bucket)
|
||||
console.log(`[i] Created bucket ${bucket}: ${ok}`)
|
||||
}
|
||||
|
||||
// create @mtcute scope if it doesn't exist
|
||||
const resp = await fetch(`${API_URL}api/scopes`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': 'token=token',
|
||||
},
|
||||
body: JSON.stringify({ scope: 'mtcute' }),
|
||||
})
|
||||
|
||||
if (resp.status !== 200 && resp.status !== 409) {
|
||||
throw new Error(`Failed to create scope: ${resp.statusText} ${await resp.text()}`)
|
||||
}
|
||||
|
||||
if (resp.status === 200) {
|
||||
console.log('[i] Created scope mtcute')
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
events {}
|
||||
|
||||
http {
|
||||
upstream gcs {
|
||||
server jsr-gcs:4080;
|
||||
}
|
||||
|
||||
upstream api {
|
||||
server jsr-api:8001;
|
||||
}
|
||||
|
||||
error_log /error.log debug;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location ~ ^/(@.*)$ {
|
||||
proxy_pass http://gcs/storage/v1/b/modules/o/$1?alt=media;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://api;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
import { tl, User } from '@mtcute/core'
|
||||
import { BaseTelegramClient, TelegramClient } from '@mtcute/core/client.js'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
import { getApiParams } from '../utils.ts'
|
||||
|
||||
function getAccountId() {
|
||||
return Math.floor(Math.random() * 10000)
|
||||
.toString()
|
||||
.padStart(4, '0')
|
||||
}
|
||||
|
||||
async function authorizeInDc(dc: number, base: BaseTelegramClient) {
|
||||
const tg = new TelegramClient({ client: base })
|
||||
|
||||
while (true) {
|
||||
await base.mt.storage.load()
|
||||
await base.storage.clear(true)
|
||||
|
||||
const phone = `99966${dc}${getAccountId()}`
|
||||
|
||||
let user
|
||||
|
||||
try {
|
||||
const sentCode = await tg.sendCode({ phone })
|
||||
|
||||
let auth = await tg.call({
|
||||
_: 'auth.signIn',
|
||||
phoneNumber: phone,
|
||||
phoneCode: `${dc}${dc}${dc}${dc}${dc}`,
|
||||
phoneCodeHash: sentCode.phoneCodeHash,
|
||||
})
|
||||
|
||||
if (auth._ === 'auth.authorizationSignUpRequired') {
|
||||
auth = await tg.call({
|
||||
_: 'auth.signUp',
|
||||
phoneNumber: phone,
|
||||
phoneCodeHash: sentCode.phoneCodeHash,
|
||||
firstName: 'mtcute e2e',
|
||||
lastName: '',
|
||||
})
|
||||
|
||||
if (auth._ !== 'auth.authorization') {
|
||||
throw new Error('Unexpected response')
|
||||
}
|
||||
}
|
||||
|
||||
await tg.notifyLoggedIn(auth)
|
||||
|
||||
user = new User(auth.user)
|
||||
} catch (e) {
|
||||
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED') || tl.RpcError.is(e, 'PHONE_NUMBER_FLOOD')) {
|
||||
// retry with another number
|
||||
await tg.close()
|
||||
continue
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
await tg.close()
|
||||
|
||||
assertEquals(user.isSelf, true)
|
||||
assertEquals(user.phoneNumber, phone)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Deno.test('1. authorization', { sanitizeResources: false }, async (t) => {
|
||||
await t.step('should authorize in default dc', async () => {
|
||||
const base = new BaseTelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
await authorizeInDc(2, base)
|
||||
})
|
||||
|
||||
await t.step('should authorize in dc 1', async () => {
|
||||
const base = new BaseTelegramClient(getApiParams('dc1.session'))
|
||||
|
||||
await authorizeInDc(1, base)
|
||||
})
|
||||
})
|
|
@ -1,46 +0,0 @@
|
|||
import { MtPeerNotFoundError } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
import { getApiParams } from '../utils.ts'
|
||||
|
||||
Deno.test('2. calling methods', { sanitizeResources: false }, async (t) => {
|
||||
const tg = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
await tg.connect()
|
||||
|
||||
await t.step('getUsers(@BotFather)', async () => {
|
||||
const [user] = await tg.getUsers('botfather')
|
||||
|
||||
assertEquals(user?.isBot, true)
|
||||
assertEquals(user?.displayName, 'BotFather')
|
||||
})
|
||||
|
||||
await t.step('getUsers(@BotFather) - cached', async () => {
|
||||
const [user] = await tg.getUsers('botfather')
|
||||
|
||||
assertEquals(user?.isBot, true)
|
||||
assertEquals(user?.displayName, 'BotFather')
|
||||
})
|
||||
|
||||
await t.step('getHistory(777000)', async () => {
|
||||
try {
|
||||
await tg.findDialogs(777000) // ensure it's cached
|
||||
} catch (e) {
|
||||
if (e instanceof MtPeerNotFoundError) {
|
||||
// this happens sometimes :D gracefully skip
|
||||
return
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
const history = await tg.getHistory(777000, { limit: 5 })
|
||||
|
||||
assertEquals(history[0].chat.chatType, 'private')
|
||||
assertEquals(history[0].chat.id, 777000)
|
||||
assertEquals(history[0].chat.firstName, 'Telegram')
|
||||
})
|
||||
|
||||
await tg.close()
|
||||
})
|
|
@ -1,171 +0,0 @@
|
|||
import type { FileDownloadLocation } from '@mtcute/core'
|
||||
|
||||
import { createHash } from 'node:crypto'
|
||||
import { Thumbnail } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { sleep } from '@mtcute/core/utils.js'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
import { getApiParams } from '../utils.ts'
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
Deno.test('3. working with files', { sanitizeResources: false }, async (t) => {
|
||||
// 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
|
||||
//
|
||||
// ...except we can't under deno because it's not implemented
|
||||
// https://github.com/denoland/deno/issues/19882
|
||||
// this.retries(2)
|
||||
|
||||
await t.step('same-dc', async (t) => {
|
||||
const tg = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
await tg.connect()
|
||||
|
||||
await t.step('should download pfp thumbs', async () => {
|
||||
const chat = await tg.getChat(CINNAMOROLL_PFP_CHAT)
|
||||
if (!chat.photo) throw new Error('Chat has no photo')
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, chat.photo.big), CINNAMOROLL_PFP_THUMB_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should download animated pfps', async () => {
|
||||
const chat = await tg.getFullChat(CINNAMOROLL_PFP_CHAT)
|
||||
const thumb = chat.fullPhoto?.getThumbnail(Thumbnail.THUMB_VIDEO_PROFILE)
|
||||
if (!thumb) throw new Error('Chat has no animated pfp')
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, thumb), CINNAMOROLL_PFP_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should download photos', async () => {
|
||||
const msg = await tg.getMessageByLink(UWU_MSG)
|
||||
|
||||
if (msg?.media?.type !== 'photo') {
|
||||
throw new Error('Message not found or not a photo')
|
||||
}
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, msg.media), UWU_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should download documents', async () => {
|
||||
const msg = await tg.getMessageByLink(SHREK_MSG)
|
||||
|
||||
if (msg?.media?.type !== 'document') {
|
||||
throw new Error('Message not found or not a document')
|
||||
}
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, msg.media), SHREK_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should cancel downloads', async () => {
|
||||
const msg = await tg.getMessageByLink(LARGE_MSG)
|
||||
|
||||
if (msg?.media?.type !== 'document') {
|
||||
throw new Error('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
|
||||
|
||||
assertEquals(downloaded, downloadedBefore, 'nothing should be downloaded after abort')
|
||||
})
|
||||
|
||||
await tg.close()
|
||||
})
|
||||
|
||||
await t.step('cross-dc', async (t) => {
|
||||
const tg = new TelegramClient(getApiParams('dc1.session'))
|
||||
|
||||
await tg.connect()
|
||||
|
||||
await t.step('should download pfp thumbs', async () => {
|
||||
const chat = await tg.getChat(CINNAMOROLL_PFP_CHAT)
|
||||
if (!chat.photo) throw new Error('Chat has no photo')
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, chat.photo.big), CINNAMOROLL_PFP_THUMB_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should download animated pfps', async () => {
|
||||
const chat = await tg.getFullChat(CINNAMOROLL_PFP_CHAT)
|
||||
const thumb = chat.fullPhoto?.getThumbnail(Thumbnail.THUMB_VIDEO_PROFILE)
|
||||
if (!thumb) throw new Error('Chat has no animated pfp')
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, thumb), CINNAMOROLL_PFP_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should download photos', async () => {
|
||||
const msg = await tg.getMessageByLink(UWU_MSG)
|
||||
|
||||
if (msg?.media?.type !== 'photo') {
|
||||
throw new Error('Message not found or not a photo')
|
||||
}
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, msg.media), UWU_SHA256)
|
||||
})
|
||||
|
||||
await t.step('should download documents', async () => {
|
||||
const msg = await tg.getMessageByLink(SHREK_MSG)
|
||||
|
||||
if (msg?.media?.type !== 'document') {
|
||||
throw new Error('Message not found or not a document')
|
||||
}
|
||||
|
||||
assertEquals(await downloadAsSha256(tg, msg.media), SHREK_SHA256)
|
||||
})
|
||||
|
||||
await tg.close()
|
||||
})
|
||||
})
|
|
@ -1,46 +0,0 @@
|
|||
import type { Message } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { assertEquals, assertNotEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
import { getApiParams, waitFor } from '../utils.ts'
|
||||
|
||||
Deno.test('4. handling updates', { sanitizeResources: false }, async (t) => {
|
||||
const tg1 = new TelegramClient(getApiParams('dc1.session'))
|
||||
tg1.log.prefix = '[tg1] '
|
||||
const tg2 = new TelegramClient(getApiParams('dc2.session'))
|
||||
tg2.log.prefix = '[tg2] '
|
||||
|
||||
await tg1.connect()
|
||||
await tg1.startUpdatesLoop()
|
||||
await tg2.connect()
|
||||
|
||||
await t.step('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)
|
||||
|
||||
assertEquals(sentMsg.text, messageText)
|
||||
assertEquals(sentMsg.chat.id, tg1User!.id)
|
||||
|
||||
await waitFor(() => {
|
||||
assertNotEquals(
|
||||
tg1Messages.find(msg => msg.text === messageText),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
await tg1.close()
|
||||
await tg2.close()
|
||||
})
|
|
@ -1,85 +0,0 @@
|
|||
import type { Message } from '@mtcute/deno'
|
||||
import type { CustomMethods } from './_worker.ts'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { Long, TelegramWorkerPort, tl } from '@mtcute/deno'
|
||||
|
||||
import { assertEquals, assertGreater, assertInstanceOf } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
import { getApiParams, waitFor } from '../utils.ts'
|
||||
|
||||
Deno.test('5. worker', { sanitizeResources: false }, async (t) => {
|
||||
const worker = new Worker(new URL('_worker.ts', import.meta.url), {
|
||||
type: 'module',
|
||||
})
|
||||
|
||||
const port = new TelegramWorkerPort<CustomMethods>({
|
||||
worker,
|
||||
})
|
||||
const portClient = new TelegramClient({ client: port })
|
||||
|
||||
await t.step('should make api calls', async () => {
|
||||
const res = await port.call({ _: 'help.getConfig' })
|
||||
assertEquals(res._, 'config')
|
||||
|
||||
const premiumPromo = await port.call({ _: 'help.getPremiumPromo' })
|
||||
// ensure Long-s are correctly serialized
|
||||
assertEquals(Long.isLong((premiumPromo.users[0] as tl.RawUser).accessHash), true)
|
||||
})
|
||||
|
||||
await t.step('should call custom methods', async () => {
|
||||
const hello = await port.invokeCustom('hello')
|
||||
assertEquals(hello, 'world')
|
||||
|
||||
const sum = await port.invokeCustom('sum', 2, 3)
|
||||
assertEquals(sum, 5)
|
||||
})
|
||||
|
||||
await t.step('should throw errors', async () => {
|
||||
try {
|
||||
await port.call({ _: 'test.useConfigSimple' })
|
||||
throw new Error('should have thrown')
|
||||
} catch (e) {
|
||||
assertInstanceOf(e, tl.RpcError)
|
||||
}
|
||||
})
|
||||
|
||||
await t.step('should receive updates', async () => {
|
||||
const client2 = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
try {
|
||||
await client2.connect()
|
||||
await port.startUpdatesLoop()
|
||||
|
||||
const me = await portClient.getMe()
|
||||
// ensure Long-s are correctly serialized
|
||||
assertEquals(Long.isLong(me.raw.accessHash), true)
|
||||
let username = me.username
|
||||
|
||||
if (!username) {
|
||||
username = `mtcute_e2e_${Math.random().toString(36).slice(2, 8)}`
|
||||
await portClient.setMyUsername(username)
|
||||
}
|
||||
|
||||
const msgs: Message[] = []
|
||||
portClient.on('new_message', (msg) => {
|
||||
msgs.push(msg)
|
||||
})
|
||||
|
||||
const testText = `test ${Math.random()}`
|
||||
await client2.sendText(username, testText)
|
||||
|
||||
await waitFor(() => {
|
||||
assertGreater(msgs.length, 0)
|
||||
assertEquals(msgs[0].text, testText)
|
||||
})
|
||||
} catch (e) {
|
||||
await client2.close()
|
||||
throw e
|
||||
}
|
||||
|
||||
await client2.close()
|
||||
})
|
||||
|
||||
await port.close()
|
||||
worker.terminate()
|
||||
})
|
|
@ -1,18 +0,0 @@
|
|||
import type { WorkerCustomMethods } from '@mtcute/core/worker.js'
|
||||
import { BaseTelegramClient, TelegramWorker } from '@mtcute/deno'
|
||||
|
||||
import { getApiParams } from '../utils.ts'
|
||||
|
||||
const customMethods = {
|
||||
hello: async () => 'world',
|
||||
sum: async (a: number, b: number) => a + b,
|
||||
} as const satisfies WorkerCustomMethods
|
||||
export type CustomMethods = typeof customMethods
|
||||
|
||||
const client = new BaseTelegramClient(getApiParams('dc1.session'))
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new TelegramWorker({
|
||||
client,
|
||||
customMethods,
|
||||
})
|
|
@ -1,20 +0,0 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core/client.js'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
import { getApiParams } from '../../utils.ts'
|
||||
|
||||
Deno.test('@mtcute/core', async (t) => {
|
||||
await t.step('connects to test DC and makes help.getNearestDc', async () => {
|
||||
const tg = new BaseTelegramClient({
|
||||
...getApiParams(),
|
||||
})
|
||||
|
||||
await tg.connect()
|
||||
const config = await tg.call({ _: 'help.getNearestDc' })
|
||||
await tg.close()
|
||||
|
||||
assertEquals(typeof config, 'object')
|
||||
assertEquals(config._, 'nearestDc')
|
||||
assertEquals(config.thisDc, 2)
|
||||
})
|
||||
})
|
|
@ -1,71 +0,0 @@
|
|||
import { Long } from '@mtcute/core'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||
import { WebPlatform } from '@mtcute/web'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
// here we primarily want to check that everything imports properly,
|
||||
// and that the code is actually executable. The actual correctness
|
||||
// of the implementation is covered tested by unit tests
|
||||
|
||||
const p = new WebPlatform()
|
||||
setPlatform(p)
|
||||
|
||||
Deno.test('encodings', () => {
|
||||
assertEquals(p.hexEncode(new Uint8Array([1, 2, 3, 4, 5])), '0102030405')
|
||||
})
|
||||
|
||||
Deno.test('TlBinaryReader', () => {
|
||||
const map = {
|
||||
85337187(r: any) {
|
||||
const ret: any = {}
|
||||
ret._ = 'mt_resPQ'
|
||||
ret.nonce = r.int128()
|
||||
ret.serverNonce = r.int128()
|
||||
ret.pq = r.bytes()
|
||||
ret.serverPublicKeyFingerprints = r.vector(r.long)
|
||||
|
||||
return ret
|
||||
},
|
||||
}
|
||||
const data
|
||||
= '000000000000000001c8831ec97ae55140000000632416053e0549828cca27e966b301a48fece2fca5cf4d33f4a11ea877ba4aa5739073300817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3'
|
||||
const buf = p.hexDecode(data)
|
||||
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
assertEquals(r.long().toString(16), '51e57ac91e83c801')
|
||||
assertEquals(r.uint(), 64)
|
||||
|
||||
const obj: any = r.object()
|
||||
assertEquals(obj._, 'mt_resPQ')
|
||||
})
|
||||
|
||||
Deno.test('TlBinaryWriter', () => {
|
||||
const map = {
|
||||
mt_resPQ(w: any, obj: any) {
|
||||
w.uint(85337187)
|
||||
w.bytes(obj.pq)
|
||||
w.vector(w.long, obj.serverPublicKeyFingerprints)
|
||||
},
|
||||
_staticSize: {} as any,
|
||||
}
|
||||
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: p.hexDecode('17ED48941A08F981'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
assertEquals(TlSerializationCounter.countNeededBytes(map, obj), 32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
assertEquals(
|
||||
p.hexEncode(w.result()),
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
|
@ -1,42 +0,0 @@
|
|||
import { Long } from '@mtcute/core'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
|
||||
import { WebPlatform } from '@mtcute/web'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
// here we primarily want to check that @mtcute/tl correctly works with @mtcute/tl-runtime
|
||||
|
||||
const p = new WebPlatform()
|
||||
setPlatform(p)
|
||||
|
||||
Deno.test('@mtcute/tl', async (t) => {
|
||||
await t.step('writers map works with TlBinaryWriter', () => {
|
||||
const obj = {
|
||||
_: 'inputPeerUser',
|
||||
userId: 123,
|
||||
accessHash: Long.fromNumber(456),
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
p.hexEncode(TlBinaryWriter.serializeObject(__tlWriterMap, obj)),
|
||||
'4ca5e8dd7b00000000000000c801000000000000',
|
||||
)
|
||||
})
|
||||
|
||||
await t.step('readers map works with TlBinaryReader', () => {
|
||||
const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000')
|
||||
|
||||
const obj = TlBinaryReader.deserializeObject<any>(__tlReaderMap, buf)
|
||||
|
||||
assertEquals(obj._, 'inputPeerUser')
|
||||
assertEquals(obj.userId, 123)
|
||||
assertEquals(obj.accessHash.toString(), '456')
|
||||
})
|
||||
|
||||
await t.step('correctly checks for combinator types', () => {
|
||||
assertEquals(tl.isAnyInputUser({ _: 'inputUserEmpty' }), true)
|
||||
})
|
||||
})
|
|
@ -1,24 +0,0 @@
|
|||
import { ige256Decrypt, ige256Encrypt } from '@mtcute/wasm'
|
||||
import { WebCryptoProvider, WebPlatform } from '@mtcute/web'
|
||||
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||
|
||||
await new WebCryptoProvider().initialize()
|
||||
const platform = new WebPlatform()
|
||||
|
||||
Deno.test('@mtcute/wasm', async (t) => {
|
||||
const key = platform.hexDecode('5468697320697320616E20696D706C655468697320697320616E20696D706C65')
|
||||
const iv = platform.hexDecode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353')
|
||||
|
||||
const data = platform.hexDecode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b')
|
||||
const dataEnc = platform.hexDecode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69')
|
||||
|
||||
await t.step('should work with Buffers', () => {
|
||||
assertEquals(ige256Encrypt(data, key, iv), dataEnc)
|
||||
assertEquals(ige256Decrypt(dataEnc, key, iv), data)
|
||||
})
|
||||
|
||||
await t.step('should work with Uint8Arrays', () => {
|
||||
assertEquals(ige256Encrypt(data, key, iv), dataEnc)
|
||||
assertEquals(ige256Decrypt(dataEnc, key, iv), data)
|
||||
})
|
||||
})
|
|
@ -1,43 +0,0 @@
|
|||
import type { MaybePromise } from '@mtcute/core'
|
||||
import { MemoryStorage } from '@mtcute/core'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import { LogManager, sleep } from '@mtcute/core/utils.js'
|
||||
import { DenoCryptoProvider, DenoPlatform, SqliteStorage, TcpTransport } from '@mtcute/deno'
|
||||
|
||||
export function getApiParams(storage?: string) {
|
||||
if (!Deno.env.has('API_ID') || !Deno.env.has('API_HASH')) {
|
||||
throw new Error('API_ID and API_HASH env variables must be set')
|
||||
}
|
||||
|
||||
Deno.mkdirSync('.sessions', { recursive: true })
|
||||
|
||||
setPlatform(new DenoPlatform())
|
||||
|
||||
return {
|
||||
apiId: Number.parseInt(Deno.env.get('API_ID')!),
|
||||
apiHash: Deno.env.get('API_HASH')!,
|
||||
testMode: true,
|
||||
storage: storage ? new SqliteStorage(`.sessions/${storage}`) : new MemoryStorage(),
|
||||
logLevel: LogManager.VERBOSE,
|
||||
transport: () => new TcpTransport(),
|
||||
crypto: new DenoCryptoProvider(),
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitFor(condition: () => MaybePromise<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
|
||||
}
|
23
e2e/import-map.json
Normal file
23
e2e/import-map.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"imports": {
|
||||
"@fuman/utils": "jsr:@fuman/utils@0.0.1",
|
||||
"chai": "https://esm.sh/chai@5.1.2",
|
||||
"node:test": "./tests/deno-shims/node-test.js",
|
||||
"mtcute": "../packages/deno/src/index.ts",
|
||||
"mtcute/utils.js": "../packages/deno/src/utils.ts",
|
||||
"@mtcute/core": "../packages/core/src/index.ts",
|
||||
"@mtcute/core/client.js": "../packages/core/src/highlevel/client.ts",
|
||||
"@mtcute/core/methods.js": "../packages/core/src/highlevel/methods.ts",
|
||||
"@mtcute/core/utils.js": "../packages/core/src/utils/index.ts",
|
||||
"@mtcute/core/worker.js": "../packages/core/src/highlevel/worker/index.ts",
|
||||
"@mtcute/markdown-parser": "../packages/markdown-parser/src/index.ts",
|
||||
"@mtcute/html-parser": "../packages/html-parser/src/index.ts",
|
||||
"@mtcute/file-id": "../packages/file-id/src/index.ts",
|
||||
"@mtcute/tl-runtime": "../packages/tl-runtime/src/index.ts",
|
||||
"@mtcute/wasm": "../packages/wasm/src/index.ts",
|
||||
"@mtcute/tl": "./tests/deno-shims/tl.js",
|
||||
"@mtcute/tl/binary/rsa-keys.js": "./tests/deno-shims/tl-rsa.js",
|
||||
"@mtcute/tl/binary/reader.js": "./tests/deno-shims/tl-reader.js",
|
||||
"@mtcute/tl/binary/writer.js": "./tests/deno-shims/tl-writer.js"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
.verdaccio
|
||||
node_modules
|
||||
private
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
.npmrc
|
||||
.env*
|
|
@ -1,5 +0,0 @@
|
|||
# obtain these values from my.telegram.org
|
||||
API_ID=
|
||||
API_HASH=
|
||||
|
||||
GITHUB_TOKEN=
|
4
e2e/node/.gitignore
vendored
4
e2e/node/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
.verdaccio/storage
|
||||
.npmrc
|
||||
pnpm-lock.yaml
|
||||
.env
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"node-option": []
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
storage: ./storage
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
max_users: -1
|
||||
|
||||
packages:
|
||||
'**':
|
||||
access: $all
|
||||
publish: $all
|
||||
|
||||
logs: { type: stdout, format: pretty, level: trace }
|
|
@ -1 +0,0 @@
|
|||
mtcute-bot:$apr1$7rbqxva0$zyfFgknsbAxni.cq158Sf.
|
|
@ -1,20 +0,0 @@
|
|||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add python3 py3-pip make g++ && \
|
||||
python3 -m pip install --break-system-packages setuptools && \
|
||||
corepack enable && \
|
||||
corepack prepare pnpm@9.0.6 --activate
|
||||
|
||||
COPY ../.. /app/
|
||||
|
||||
RUN pnpm install --frozen-lockfile && \
|
||||
pnpm -C packages/tl run gen-code && \
|
||||
# verdaccio is configured to allow anonymous publish, but npm requires a token 🥴
|
||||
npm config set //verdaccio:4873/:_authToken fake-token
|
||||
|
||||
ENV REGISTRY="http://verdaccio:4873/"
|
||||
ENV E2E="1"
|
||||
|
||||
ENTRYPOINT [ "node", "/app/scripts/publish.js" ]
|
||||
CMD [ "all" ]
|
|
@ -1,14 +0,0 @@
|
|||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add python3 py3-pip make g++ && \
|
||||
python3 -m pip install --break-system-packages setuptools && \
|
||||
corepack enable && \
|
||||
corepack prepare pnpm@9.0.6 --activate
|
||||
|
||||
COPY ./ /app/
|
||||
RUN npm config set -L project @mtcute:registry http://verdaccio:4873/ && \
|
||||
chmod +x ./docker-entrypoint.sh
|
||||
|
||||
ENTRYPOINT [ "./docker-entrypoint.sh" ]
|
||||
CMD [ "all" ]
|
|
@ -1,48 +0,0 @@
|
|||
# mtcute e2e tests
|
||||
|
||||
This directory contains end-to-end tests for mtcute.
|
||||
|
||||
They are made for 2 purposes:
|
||||
- Ensure published packages work as expected and can properly be imported
|
||||
- Ensure that the library works with the actual Telegram API (WIP)
|
||||
|
||||
To achieve the first goal, we use a Verdaccio container to publish the package to,
|
||||
and then install it from there in another container
|
||||
|
||||
## Setting up
|
||||
|
||||
Before running the tests, you need to copy `.env.example` to `.env` and fill in the values
|
||||
|
||||
## Running tests
|
||||
|
||||
To run tests, you need to have Docker installed.
|
||||
|
||||
```bash
|
||||
# first start Verdaccio:
|
||||
./cli.sh start
|
||||
|
||||
# build and publish the package
|
||||
./cli.sh update
|
||||
# or a particular package
|
||||
./cli.sh update tl-runtime
|
||||
|
||||
# run the tests
|
||||
./cli.sh run
|
||||
# or in docker
|
||||
./cli.sh run-docker
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you have Verdaccio running, you can run the following commands to setup
|
||||
the environment for development:
|
||||
|
||||
```bash
|
||||
npm config set -L project @mtcute:registry http://verdaccio.e2e.orb.local/
|
||||
./cli.sh install
|
||||
```
|
||||
|
||||
> Replace the URL above with the one generated with your Docker GUI of choice
|
||||
> (e2e > verdaccio > RMB > Open in browser). Example above assumes OrbStack
|
||||
|
||||
Then use `./cli.sh run` to run the tests
|
|
@ -1 +0,0 @@
|
|||
{}
|
|
@ -1,23 +0,0 @@
|
|||
const { BaseTelegramClient } = require('@mtcute/core/client.js')
|
||||
const { expect } = require('chai')
|
||||
const { describe, it } = require('mocha')
|
||||
|
||||
const { getApiParams } = require('../utils')
|
||||
|
||||
describe('@mtcute/core', function () {
|
||||
this.timeout(300_000)
|
||||
|
||||
it('connects to test DC and makes help.getNearestDc', async () => {
|
||||
const tg = new BaseTelegramClient({
|
||||
...getApiParams(),
|
||||
})
|
||||
|
||||
await tg.connect()
|
||||
const config = await tg.call({ _: 'help.getNearestDc' })
|
||||
await tg.close()
|
||||
|
||||
expect(config).to.be.an('object')
|
||||
expect(config._).to.equal('nearestDc')
|
||||
expect(config.thisDc).to.equal(2)
|
||||
})
|
||||
})
|
|
@ -1,112 +0,0 @@
|
|||
const { NodePlatform } = require('@mtcute/node')
|
||||
const { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } = require('@mtcute/tl-runtime')
|
||||
const { expect } = require('chai')
|
||||
const Long = require('long')
|
||||
const { describe, it } = require('mocha')
|
||||
|
||||
// here we primarily want to check that everything imports properly,
|
||||
// and that the code is actually executable. The actual correctness
|
||||
// of the implementation is covered tested by unit tests
|
||||
|
||||
const p = new NodePlatform()
|
||||
|
||||
describe('@mtcute/tl-runtime', () => {
|
||||
describe('encodings', () => {
|
||||
it('works with Buffers', () => {
|
||||
expect(p.hexEncode(Buffer.from('hello'))).to.equal('68656c6c6f')
|
||||
expect(p.hexDecode('0102030405')).eql(Buffer.from([1, 2, 3, 4, 5]))
|
||||
})
|
||||
|
||||
it('works with Uint8Arrays', () => {
|
||||
expect(p.hexEncode(new Uint8Array([1, 2, 3, 4, 5]))).to.equal('0102030405')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TlBinaryReader', () => {
|
||||
const map = {
|
||||
85337187(r) {
|
||||
const ret = {}
|
||||
ret._ = 'mt_resPQ'
|
||||
ret.nonce = r.int128()
|
||||
ret.serverNonce = r.int128()
|
||||
ret.pq = r.bytes()
|
||||
ret.serverPublicKeyFingerprints = r.vector(r.long)
|
||||
|
||||
return ret
|
||||
},
|
||||
}
|
||||
const data
|
||||
= '000000000000000001c8831ec97ae55140000000632416053e0549828cca27e966b301a48fece2fca5cf4d33f4a11ea877ba4aa5739073300817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3'
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
const buf = Buffer.from(data, 'hex')
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
expect(r.long().toString(16)).to.equal('51e57ac91e83c801')
|
||||
expect(r.uint()).to.equal(64)
|
||||
|
||||
const obj = r.object()
|
||||
expect(obj._).equal('mt_resPQ')
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
const buf = p.hexDecode(data)
|
||||
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
expect(r.long().toString(16)).to.equal('51e57ac91e83c801')
|
||||
expect(r.uint()).to.equal(64)
|
||||
|
||||
const obj = r.object()
|
||||
expect(obj._).equal('mt_resPQ')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TlBinaryWriter', () => {
|
||||
const map = {
|
||||
mt_resPQ(w, obj) {
|
||||
w.uint(85337187)
|
||||
w.bytes(obj.pq)
|
||||
w.vector(w.long, obj.serverPublicKeyFingerprints)
|
||||
},
|
||||
}
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: Buffer.from('17ED48941A08F981', 'hex'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
expect(TlSerializationCounter.countNeededBytes(map, obj)).to.equal(32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
expect(p.hexEncode(w.result())).eq(
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: p.hexDecode('17ED48941A08F981'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
expect(TlSerializationCounter.countNeededBytes(map, obj)).to.equal(32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
expect(p.hexEncode(w.result())).eq(
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,39 +0,0 @@
|
|||
const { NodePlatform } = require('@mtcute/node')
|
||||
const { tl } = require('@mtcute/tl')
|
||||
const { TlBinaryReader, TlBinaryWriter } = require('@mtcute/tl-runtime')
|
||||
const { __tlReaderMap } = require('@mtcute/tl/binary/reader')
|
||||
const { __tlWriterMap } = require('@mtcute/tl/binary/writer')
|
||||
const { expect } = require('chai')
|
||||
const Long = require('long')
|
||||
const { describe, it } = require('mocha')
|
||||
|
||||
// here we primarily want to check that @mtcute/tl correctly works with @mtcute/tl-runtime
|
||||
|
||||
const p = new NodePlatform()
|
||||
|
||||
describe('@mtcute/tl', () => {
|
||||
it('writers map works with TlBinaryWriter', () => {
|
||||
const obj = {
|
||||
_: 'inputPeerUser',
|
||||
userId: 123,
|
||||
accessHash: Long.fromNumber(456),
|
||||
}
|
||||
|
||||
expect(p.hexEncode(TlBinaryWriter.serializeObject(__tlWriterMap, obj))).to.equal(
|
||||
'4ca5e8dd7b00000000000000c801000000000000',
|
||||
)
|
||||
})
|
||||
|
||||
it('readers map works with TlBinaryReader', () => {
|
||||
const buf = Buffer.from('4ca5e8dd7b00000000000000c801000000000000', 'hex')
|
||||
const obj = TlBinaryReader.deserializeObject(__tlReaderMap, buf)
|
||||
|
||||
expect(obj._).equal('inputPeerUser')
|
||||
expect(obj.userId).equal(123)
|
||||
expect(obj.accessHash.toString()).equal('456')
|
||||
})
|
||||
|
||||
it('correctly checks for combinator types', () => {
|
||||
expect(tl.isAnyInputUser({ _: 'inputUserEmpty' })).to.eq(true)
|
||||
})
|
||||
})
|
|
@ -1,30 +0,0 @@
|
|||
const { NodeCryptoProvider } = require('@mtcute/node/utils.js')
|
||||
const wasm = require('@mtcute/wasm')
|
||||
const { expect } = require('chai')
|
||||
const { describe, it, before } = require('mocha')
|
||||
|
||||
before(async () => {
|
||||
await new NodeCryptoProvider().initialize()
|
||||
})
|
||||
|
||||
describe('@mtcute/wasm', () => {
|
||||
const key = Buffer.from('5468697320697320616E20696D706C655468697320697320616E20696D706C65', 'hex')
|
||||
const iv = Buffer.from('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353', 'hex')
|
||||
|
||||
const data = Buffer.from('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b', 'hex')
|
||||
const dataEnc = Buffer.from('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69', 'hex')
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
expect(wasm.ige256Encrypt(data, key, iv)).to.deep.equal(new Uint8Array(dataEnc))
|
||||
expect(wasm.ige256Decrypt(dataEnc, key, iv)).to.deep.equal(new Uint8Array(data))
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
expect(wasm.ige256Encrypt(new Uint8Array(data), new Uint8Array(key), new Uint8Array(iv))).to.deep.equal(
|
||||
new Uint8Array(dataEnc),
|
||||
)
|
||||
expect(wasm.ige256Decrypt(new Uint8Array(dataEnc), new Uint8Array(key), new Uint8Array(iv))).to.deep.equal(
|
||||
new Uint8Array(data),
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,23 +0,0 @@
|
|||
const { MemoryStorage } = require('@mtcute/core')
|
||||
const { setPlatform } = require('@mtcute/core/platform.js')
|
||||
const { LogManager } = require('@mtcute/core/utils.js')
|
||||
const { NodePlatform, TcpTransport } = require('@mtcute/node')
|
||||
const { NodeCryptoProvider } = require('@mtcute/node/utils.js')
|
||||
|
||||
exports.getApiParams = () => {
|
||||
if (!process.env.API_ID || !process.env.API_HASH) {
|
||||
throw new Error('API_ID and API_HASH env variables must be set')
|
||||
}
|
||||
|
||||
setPlatform(new NodePlatform())
|
||||
|
||||
return {
|
||||
apiId: Number.parseInt(process.env.API_ID),
|
||||
apiHash: process.env.API_HASH,
|
||||
testMode: true,
|
||||
storage: new MemoryStorage(),
|
||||
logLevel: LogManager.DEBUG,
|
||||
transport: () => new TcpTransport(),
|
||||
crypto: new NodeCryptoProvider(),
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eau
|
||||
|
||||
method=$1
|
||||
shift
|
||||
|
||||
case "$method" in
|
||||
"run")
|
||||
node runner.js $@
|
||||
;;
|
||||
"run-docker")
|
||||
source .env
|
||||
docker compose run --rm --build test $@
|
||||
;;
|
||||
"update")
|
||||
docker compose run --build build $@
|
||||
./cli.sh install
|
||||
;;
|
||||
"start")
|
||||
docker compose up -d verdaccio
|
||||
;;
|
||||
"stop")
|
||||
docker compose down
|
||||
;;
|
||||
"install")
|
||||
rm -rf pnpm-lock.yaml node_modules
|
||||
pnpm install
|
||||
;;
|
||||
"ci")
|
||||
set -eaux
|
||||
chmod -R 777 .verdaccio
|
||||
docker compose up -d verdaccio
|
||||
docker compose run --rm --build build
|
||||
docker compose run --build test
|
||||
;;
|
||||
"ci-publish")
|
||||
export CURRENT_COMMIT=$(git rev-parse HEAD)
|
||||
docker compose up -d verdaccio
|
||||
node publish-canary.js
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command"
|
||||
;;
|
||||
esac
|
|
@ -1,30 +0,0 @@
|
|||
module.exports = {
|
||||
cjs: {
|
||||
getFiles: () => 'tests/**/*.js',
|
||||
runFile: file => `mocha ${file}`,
|
||||
},
|
||||
esm: {
|
||||
getFiles: () => 'tests/**/*.js',
|
||||
runFile: file => `mocha ${file}`,
|
||||
},
|
||||
ts: {
|
||||
getFiles: () => 'tests/**/*.ts',
|
||||
beforeAll: () => ['tsc', 'node build-esm.cjs'],
|
||||
runFile: (file) => {
|
||||
if (require('node:path').basename(file)[0] === '_') return null
|
||||
|
||||
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')}`
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
version: "3"
|
||||
services:
|
||||
verdaccio:
|
||||
restart: unless-stopped
|
||||
image: verdaccio/verdaccio:5.27
|
||||
container_name: "verdaccio"
|
||||
volumes:
|
||||
- "./.verdaccio:/verdaccio/conf"
|
||||
ports:
|
||||
- "4873:4873"
|
||||
networks:
|
||||
- mtcute-e2e
|
||||
build:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: e2e/node/Dockerfile.build
|
||||
environment:
|
||||
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||
networks:
|
||||
- mtcute-e2e
|
||||
depends_on:
|
||||
- verdaccio
|
||||
test:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.test
|
||||
environment:
|
||||
- API_ID=${API_ID}
|
||||
- API_HASH=${API_HASH}
|
||||
networks:
|
||||
- mtcute-e2e
|
||||
depends_on:
|
||||
- verdaccio
|
||||
networks:
|
||||
mtcute-e2e: {}
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# we can't do this during build because we don't have network access
|
||||
pnpm install
|
||||
|
||||
node runner.js $@
|
|
@ -1 +0,0 @@
|
|||
{ "type": "module" }
|
|
@ -1,23 +0,0 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core/client.js'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { getApiParams } from '../utils.js'
|
||||
|
||||
describe('@mtcute/core', function () {
|
||||
this.timeout(300_000)
|
||||
|
||||
it('connects to test DC and makes help.getNearestDc', async () => {
|
||||
const tg = new BaseTelegramClient({
|
||||
...getApiParams(),
|
||||
})
|
||||
|
||||
await tg.connect()
|
||||
const config = await tg.call({ _: 'help.getNearestDc' })
|
||||
await tg.close()
|
||||
|
||||
expect(config).to.be.an('object')
|
||||
expect(config._).to.equal('nearestDc')
|
||||
expect(config.thisDc).to.equal(2)
|
||||
})
|
||||
})
|
|
@ -1,110 +0,0 @@
|
|||
import { NodePlatform } from '@mtcute/node'
|
||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||
import { expect } from 'chai'
|
||||
import Long from 'long'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
// here we primarily want to check that everything imports properly,
|
||||
// and that the code is actually executable. The actual correctness
|
||||
// of the implementation is covered tested by unit tests
|
||||
|
||||
const p = new NodePlatform()
|
||||
|
||||
describe('encodings', () => {
|
||||
it('works with Buffers', () => {
|
||||
expect(p.hexEncode(Buffer.from('hello'))).to.equal('68656c6c6f')
|
||||
expect(p.hexDecode('0102030405')).eql(Buffer.from([1, 2, 3, 4, 5]))
|
||||
})
|
||||
|
||||
it('works with Uint8Arrays', () => {
|
||||
expect(p.hexEncode(new Uint8Array([1, 2, 3, 4, 5]))).to.equal('0102030405')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TlBinaryReader', () => {
|
||||
const map = {
|
||||
85337187(r) {
|
||||
const ret = {}
|
||||
ret._ = 'mt_resPQ'
|
||||
ret.nonce = r.int128()
|
||||
ret.serverNonce = r.int128()
|
||||
ret.pq = r.bytes()
|
||||
ret.serverPublicKeyFingerprints = r.vector(r.long)
|
||||
|
||||
return ret
|
||||
},
|
||||
}
|
||||
const data
|
||||
= '000000000000000001c8831ec97ae55140000000632416053e0549828cca27e966b301a48fece2fca5cf4d33f4a11ea877ba4aa5739073300817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3'
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
const buf = Buffer.from(data, 'hex')
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
expect(r.long().toString(16)).to.equal('51e57ac91e83c801')
|
||||
expect(r.uint()).to.equal(64)
|
||||
|
||||
const obj = r.object()
|
||||
expect(obj._).equal('mt_resPQ')
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
const buf = p.hexDecode(data)
|
||||
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
expect(r.long().toString(16)).to.equal('51e57ac91e83c801')
|
||||
expect(r.uint()).to.equal(64)
|
||||
|
||||
const obj = r.object()
|
||||
expect(obj._).equal('mt_resPQ')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TlBinaryWriter', () => {
|
||||
const map = {
|
||||
mt_resPQ(w, obj) {
|
||||
w.uint(85337187)
|
||||
w.bytes(obj.pq)
|
||||
w.vector(w.long, obj.serverPublicKeyFingerprints)
|
||||
},
|
||||
}
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: Buffer.from('17ED48941A08F981', 'hex'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
expect(TlSerializationCounter.countNeededBytes(map, obj)).to.equal(32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
expect(p.hexEncode(w.result())).eq(
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: p.hexDecode('17ED48941A08F981'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
expect(TlSerializationCounter.countNeededBytes(map, obj)).to.equal(32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
expect(p.hexEncode(w.result())).eq(
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,39 +0,0 @@
|
|||
import { NodePlatform } from '@mtcute/node'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
|
||||
import { expect } from 'chai'
|
||||
import Long from 'long'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
// here we primarily want to check that @mtcute/tl correctly works with @mtcute/tl-runtime
|
||||
|
||||
const p = new NodePlatform()
|
||||
|
||||
describe('@mtcute/tl', () => {
|
||||
it('writers map works with TlBinaryWriter', () => {
|
||||
const obj = {
|
||||
_: 'inputPeerUser',
|
||||
userId: 123,
|
||||
accessHash: Long.fromNumber(456),
|
||||
}
|
||||
|
||||
expect(p.hexEncode(TlBinaryWriter.serializeObject(__tlWriterMap, obj))).to.equal(
|
||||
'4ca5e8dd7b00000000000000c801000000000000',
|
||||
)
|
||||
})
|
||||
|
||||
it('readers map works with TlBinaryReader', () => {
|
||||
const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000')
|
||||
const obj = TlBinaryReader.deserializeObject(__tlReaderMap, buf)
|
||||
|
||||
expect(obj._).equal('inputPeerUser')
|
||||
expect(obj.userId).equal(123)
|
||||
expect(obj.accessHash.toString()).equal('456')
|
||||
})
|
||||
|
||||
it('correctly checks for combinator types', () => {
|
||||
expect(tl.isAnyInputUser({ _: 'inputUserEmpty' })).to.eq(true)
|
||||
})
|
||||
})
|
|
@ -1,30 +0,0 @@
|
|||
import { NodeCryptoProvider } from '@mtcute/node/utils.js'
|
||||
import { ige256Decrypt, ige256Encrypt } from '@mtcute/wasm'
|
||||
import { expect } from 'chai'
|
||||
import { before, describe, it } from 'mocha'
|
||||
|
||||
before(async () => {
|
||||
await new NodeCryptoProvider().initialize()
|
||||
})
|
||||
|
||||
describe('@mtcute/wasm', () => {
|
||||
const key = Buffer.from('5468697320697320616E20696D706C655468697320697320616E20696D706C65', 'hex')
|
||||
const iv = Buffer.from('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353', 'hex')
|
||||
|
||||
const data = Buffer.from('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b', 'hex')
|
||||
const dataEnc = Buffer.from('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69', 'hex')
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
expect(ige256Encrypt(data, key, iv)).to.deep.equal(new Uint8Array(dataEnc))
|
||||
expect(ige256Decrypt(dataEnc, key, iv)).to.deep.equal(new Uint8Array(data))
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
expect(ige256Encrypt(new Uint8Array(data), new Uint8Array(key), new Uint8Array(iv))).to.deep.equal(
|
||||
new Uint8Array(dataEnc),
|
||||
)
|
||||
expect(ige256Decrypt(new Uint8Array(dataEnc), new Uint8Array(key), new Uint8Array(iv))).to.deep.equal(
|
||||
new Uint8Array(data),
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,23 +0,0 @@
|
|||
import { MemoryStorage } from '@mtcute/core'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import { LogManager } from '@mtcute/core/utils.js'
|
||||
import { NodePlatform, TcpTransport } from '@mtcute/node'
|
||||
import { NodeCryptoProvider } from '@mtcute/node/utils.js'
|
||||
|
||||
export function getApiParams() {
|
||||
if (!process.env.API_ID || !process.env.API_HASH) {
|
||||
throw new Error('API_ID and API_HASH env variables must be set')
|
||||
}
|
||||
|
||||
setPlatform(new NodePlatform())
|
||||
|
||||
return {
|
||||
apiId: Number.parseInt(process.env.API_ID),
|
||||
apiHash: process.env.API_HASH,
|
||||
testMode: true,
|
||||
storage: new MemoryStorage(),
|
||||
logLevel: LogManager.DEBUG,
|
||||
transport: () => new TcpTransport(),
|
||||
crypto: new NodeCryptoProvider(),
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "mtcute-e2e",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@mtcute/bun": "*",
|
||||
"@mtcute/core": "*",
|
||||
"@mtcute/crypto-node": "*",
|
||||
"@mtcute/dispatcher": "*",
|
||||
"@mtcute/file-id": "*",
|
||||
"@mtcute/html-parser": "*",
|
||||
"@mtcute/http-proxy": "*",
|
||||
"@mtcute/i18n": "*",
|
||||
"@mtcute/markdown-parser": "*",
|
||||
"@mtcute/mtproxy": "*",
|
||||
"@mtcute/node": "*",
|
||||
"@mtcute/socks-proxy": "*",
|
||||
"@mtcute/tl": "*",
|
||||
"@mtcute/tl-runtime": "*",
|
||||
"@mtcute/tl-utils": "*",
|
||||
"@mtcute/wasm": "*",
|
||||
"@mtcute/web": "*",
|
||||
"@types/chai": "^4.3.8",
|
||||
"@types/mocha": "^10.0.2",
|
||||
"chai": "^4.3.10",
|
||||
"dotenv": "16.3.1",
|
||||
"glob": "10.3.10",
|
||||
"long": "^5.2.3",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.8.10"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
packages:
|
||||
- .
|
|
@ -1,80 +0,0 @@
|
|||
// this scripts publishes our e2e-tested builds to canary npm
|
||||
// at this point, we should have all our packages installed in node_modules
|
||||
// so it should be safe to just cd into them and run `npm publish` on them
|
||||
|
||||
const { execSync } = require('node:child_process')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
// setup tokenw
|
||||
const { NPM_TOKEN, REGISTRY, CURRENT_COMMIT } = process.env
|
||||
|
||||
if (!NPM_TOKEN || !REGISTRY || !CURRENT_COMMIT) {
|
||||
console.error('Missing NPM_TOKEN, REGISTRY or CURRENT_COMMIT env variables!')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
execSync(`npm config set //${REGISTRY.replace(/^https?:\/\//, '')}/:_authToken ${NPM_TOKEN}`, { stdio: 'inherit' })
|
||||
const commit = CURRENT_COMMIT.slice(0, 7)
|
||||
|
||||
const myPkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'))
|
||||
const packages = Object.keys(myPkgJson.dependencies)
|
||||
.filter(x => x.startsWith('@mtcute/'))
|
||||
.map(x => x.slice('@mtcute/'.length))
|
||||
|
||||
const workDir = path.join(__dirname, 'temp')
|
||||
fs.mkdirSync(workDir, { recursive: true })
|
||||
|
||||
async function main() {
|
||||
const versions = {}
|
||||
|
||||
function fixDependencies(pkgJson, key) {
|
||||
if (!pkgJson[key]) return
|
||||
|
||||
const deps = pkgJson[key]
|
||||
|
||||
for (const dep of Object.keys(deps)) {
|
||||
if (!dep.startsWith('@mtcute/')) continue
|
||||
deps[dep] = versions[dep.slice('@mtcute/'.length)]
|
||||
}
|
||||
}
|
||||
|
||||
// prepare working directory
|
||||
for (const pkg of packages) {
|
||||
const data = await fetch(`http://localhost:4873/@mtcute/${pkg}`).then(x => x.json())
|
||||
const version = data['dist-tags'].latest
|
||||
const tarball = data.versions[version].dist.tarball
|
||||
|
||||
execSync(`wget -O ${pkg}.tgz ${tarball}`, { cwd: workDir, stdio: 'inherit' })
|
||||
execSync(`tar -xzf ${pkg}.tgz`, { cwd: workDir, stdio: 'inherit' })
|
||||
execSync(`rm ${pkg}.tgz`, { cwd: workDir, stdio: 'inherit' })
|
||||
execSync(`mv package ${pkg}`, { cwd: workDir, stdio: 'inherit' })
|
||||
|
||||
versions[pkg] = `${version}-git.${commit}`
|
||||
}
|
||||
|
||||
for (const pkg of packages) {
|
||||
const pkgDir = path.join(workDir, pkg)
|
||||
|
||||
const pkgJsonPath = path.join(pkgDir, 'package.json')
|
||||
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))
|
||||
|
||||
fixDependencies(pkgJson, 'dependencies')
|
||||
fixDependencies(pkgJson, 'peerDependencies')
|
||||
fixDependencies(pkgJson, 'devDependencies')
|
||||
fixDependencies(pkgJson, 'optionalDependencies')
|
||||
pkgJson.version = versions[pkg]
|
||||
|
||||
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 4))
|
||||
|
||||
execSync(`npm publish --registry ${REGISTRY} -q --tag canary`, {
|
||||
cwd: pkgDir,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
|
@ -1,136 +0,0 @@
|
|||
/* eslint-disable no-console */
|
||||
const cp = require('node:child_process')
|
||||
const path = require('node:path')
|
||||
|
||||
const glob = require('glob')
|
||||
|
||||
const env = {}
|
||||
require('dotenv').config({ processEnv: env })
|
||||
|
||||
const config = require('./config')
|
||||
|
||||
const DIRS = Object.keys(config)
|
||||
|
||||
function runForFile(dir, file, single = true) {
|
||||
const { runFile, beforeAll } = config[dir]
|
||||
|
||||
if (!runFile) {
|
||||
console.log('No runFile for %s', dir)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let cmds = runFile(file)
|
||||
|
||||
if (!cmds) {
|
||||
return
|
||||
}
|
||||
|
||||
const options = {
|
||||
env: {
|
||||
...env,
|
||||
...process.env,
|
||||
},
|
||||
cwd: path.join(__dirname, dir),
|
||||
stdio: 'inherit',
|
||||
}
|
||||
|
||||
if (!Array.isArray(cmds)) {
|
||||
cmds = [cmds]
|
||||
}
|
||||
|
||||
if (beforeAll && single) {
|
||||
cmds.unshift(...beforeAll())
|
||||
}
|
||||
|
||||
for (const c of cmds) {
|
||||
console.log('%s $ %s', dir, c)
|
||||
cp.execSync(`pnpm exec ${c}`, options)
|
||||
}
|
||||
}
|
||||
|
||||
function runForDir(dir) {
|
||||
const { getFiles, beforeAll } = config[dir]
|
||||
|
||||
if (!getFiles) {
|
||||
console.log('No getFiles for %s', dir)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const options = {
|
||||
env: {
|
||||
...env,
|
||||
...process.env,
|
||||
},
|
||||
cwd: path.join(__dirname, dir),
|
||||
stdio: 'inherit',
|
||||
}
|
||||
|
||||
if (beforeAll) {
|
||||
for (const c of beforeAll()) {
|
||||
console.log('%s $ %s', dir, c)
|
||||
cp.execSync(`pnpm exec ${c}`, options)
|
||||
}
|
||||
}
|
||||
|
||||
const files = glob.sync(getFiles(), { cwd: path.join(__dirname, dir) })
|
||||
files.sort()
|
||||
|
||||
for (const file of files) {
|
||||
runForFile(dir, file, false)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!process.argv[2]) {
|
||||
console.log('Usage: node runner.js <what>')
|
||||
console.log(' where <what> is one of:')
|
||||
console.log(' publish-canary - publish everything to canary npm')
|
||||
console.log(' all - run all tests')
|
||||
console.log(' <dirname> - (one of %s) - run tests for that directory', DIRS.join(', '))
|
||||
console.log(' <dirname> <filename> - run tests for that file')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const [dir, file] = process.argv.slice(2)
|
||||
|
||||
if (dir === 'publish-canary') {
|
||||
cp.execSync('node publish-canary.js', { stdio: 'inherit' })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (dir === 'all') {
|
||||
for (const d of DIRS) {
|
||||
console.log('Entering %s', d)
|
||||
runForDir(d)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!DIRS.includes(dir)) {
|
||||
console.log('Unknown directory %s', dir)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const files = glob.sync(config[dir].getFiles(), { cwd: path.join(__dirname, dir) })
|
||||
const matchingFile = files.find(f => f.endsWith(file))
|
||||
|
||||
if (!matchingFile) {
|
||||
console.log("Can't find file %s", file)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
runForFile(dir, matchingFile)
|
||||
} else {
|
||||
runForDir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
|
@ -1,52 +0,0 @@
|
|||
const cp = require('node:child_process')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const glob = require('glob')
|
||||
|
||||
function fixForEsm() {
|
||||
const modified = {}
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, 'package.json'), JSON.stringify({ type: 'module' }))
|
||||
|
||||
for (const file of glob.sync('tests/**/*.ts')) {
|
||||
let content = fs.readFileSync(file, 'utf8')
|
||||
|
||||
if (content.includes('@fix-import')) {
|
||||
modified[file] = content
|
||||
content = content.replace(/(?<=@fix-import\nimport.*?')(.*?)(?='$)/gms, '$1.js')
|
||||
fs.writeFileSync(file, content)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
fs.writeFileSync(path.join(__dirname, 'package.json'), JSON.stringify({ type: 'commonjs' }))
|
||||
|
||||
for (const file of Object.keys(modified)) {
|
||||
fs.writeFileSync(file, modified[file])
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.fixForEsm = fixForEsm
|
||||
|
||||
function main() {
|
||||
const restore = fixForEsm()
|
||||
let error = null
|
||||
|
||||
try {
|
||||
cp.execSync('pnpm exec tsc --outDir dist/esm', { stdio: 'inherit' })
|
||||
fs.writeFileSync(path.join(__dirname, 'dist/esm/package.json'), JSON.stringify({ type: 'module' }))
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
restore()
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main()
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"node-option": ["experimental-specifier-resolution=node", "loader=ts-node/esm"]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{ "type": "commonjs" }
|
|
@ -1,25 +0,0 @@
|
|||
const cp = require('node:child_process')
|
||||
|
||||
const { fixForEsm } = require('./build-esm.cjs')
|
||||
|
||||
const file = process.argv[2]
|
||||
|
||||
if (!file) {
|
||||
console.error('Usage: run-esm.cjs <file>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let error = null
|
||||
const restore = fixForEsm()
|
||||
|
||||
try {
|
||||
cp.execSync(`pnpm exec mocha --config=mocha.esm.json ${file}`, { stdio: 'inherit' })
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
restore()
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
import { tl, User } from '@mtcute/core'
|
||||
import { BaseTelegramClient, TelegramClient } from '@mtcute/core/client.js'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { getApiParams } from '../utils.js'
|
||||
|
||||
function getAccountId() {
|
||||
return Math.floor(Math.random() * 10000)
|
||||
.toString()
|
||||
.padStart(4, '0')
|
||||
}
|
||||
|
||||
async function authorizeInDc(dc: number, base: BaseTelegramClient) {
|
||||
const tg = new TelegramClient({ client: base })
|
||||
|
||||
while (true) {
|
||||
await base.mt.storage.load()
|
||||
await base.storage.clear(true)
|
||||
|
||||
const phone = `99966${dc}${getAccountId()}`
|
||||
|
||||
let user
|
||||
|
||||
try {
|
||||
const sentCode = await tg.sendCode({ phone })
|
||||
|
||||
let auth = await tg.call({
|
||||
_: 'auth.signIn',
|
||||
phoneNumber: phone,
|
||||
phoneCode: `${dc}${dc}${dc}${dc}${dc}`,
|
||||
phoneCodeHash: sentCode.phoneCodeHash,
|
||||
})
|
||||
|
||||
if (auth._ === 'auth.authorizationSignUpRequired') {
|
||||
auth = await tg.call({
|
||||
_: 'auth.signUp',
|
||||
phoneNumber: phone,
|
||||
phoneCodeHash: sentCode.phoneCodeHash,
|
||||
firstName: 'mtcute e2e',
|
||||
lastName: '',
|
||||
})
|
||||
|
||||
if (auth._ !== 'auth.authorization') {
|
||||
throw new Error('Unexpected response')
|
||||
}
|
||||
}
|
||||
|
||||
await tg.notifyLoggedIn(auth)
|
||||
|
||||
user = new User(auth.user)
|
||||
} catch (e) {
|
||||
if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED') || tl.RpcError.is(e, 'PHONE_NUMBER_FLOOD')) {
|
||||
// retry with another number
|
||||
await tg.close()
|
||||
continue
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
await tg.close()
|
||||
|
||||
expect(user.isSelf).to.eq(true)
|
||||
expect(user.phoneNumber).to.equal(phone)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
describe('1. authorization', function () {
|
||||
this.timeout(300_000)
|
||||
|
||||
it('should authorize in default dc', async () => {
|
||||
const base = new BaseTelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
await authorizeInDc(2, base)
|
||||
})
|
||||
|
||||
it('should authorize in dc 1', async () => {
|
||||
const base = new BaseTelegramClient(getApiParams('dc1.session'))
|
||||
|
||||
await authorizeInDc(1, base)
|
||||
})
|
||||
})
|
|
@ -1,92 +0,0 @@
|
|||
import type { Message } from '@mtcute/node'
|
||||
import type { CustomMethods } from './_worker.js'
|
||||
|
||||
import path from 'node:path'
|
||||
import { Worker } from 'node:worker_threads'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { Long, TelegramWorkerPort, tl } from '@mtcute/node'
|
||||
import { expect } from 'chai'
|
||||
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { getApiParams, waitFor } from '../utils.js'
|
||||
|
||||
describe('5. worker', function () {
|
||||
this.timeout(300_000)
|
||||
|
||||
const worker = new Worker(path.resolve(__dirname, '_worker.js'))
|
||||
|
||||
const port = new TelegramWorkerPort<CustomMethods>({
|
||||
worker,
|
||||
})
|
||||
const portClient = new TelegramClient({ client: port })
|
||||
|
||||
it('should make api calls', async () => {
|
||||
const res = await port.call({ _: 'help.getConfig' })
|
||||
expect(res._).to.equal('config')
|
||||
|
||||
const premiumPromo = await port.call({ _: 'help.getPremiumPromo' })
|
||||
// ensure Long-s are correctly serialized
|
||||
expect(Long.isLong((premiumPromo.users[0] as tl.RawUser).accessHash)).to.equal(true)
|
||||
})
|
||||
|
||||
it('should call custom methods', async () => {
|
||||
const hello = await port.invokeCustom('hello')
|
||||
expect(hello).to.equal('world')
|
||||
|
||||
const sum = await port.invokeCustom('sum', 2, 3)
|
||||
expect(sum).to.equal(5)
|
||||
})
|
||||
|
||||
it('should throw errors', async () => {
|
||||
try {
|
||||
await port.call({ _: 'test.useConfigSimple' })
|
||||
throw new Error('should have thrown')
|
||||
} catch (e) {
|
||||
expect(e).to.be.an.instanceOf(tl.RpcError)
|
||||
}
|
||||
})
|
||||
|
||||
it('should receive updates', async () => {
|
||||
const client2 = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
try {
|
||||
await client2.connect()
|
||||
await port.startUpdatesLoop()
|
||||
|
||||
const me = await portClient.getMe()
|
||||
// ensure Long-s are correctly serialized
|
||||
expect(Long.isLong(me.raw.accessHash)).equals(true)
|
||||
|
||||
let username = me.username
|
||||
|
||||
if (!username) {
|
||||
username = `mtcute_e2e_${Math.random().toString(36).slice(2, 8)}`
|
||||
await portClient.setMyUsername(username)
|
||||
}
|
||||
|
||||
const msgs: Message[] = []
|
||||
portClient.on('new_message', (msg) => {
|
||||
msgs.push(msg)
|
||||
})
|
||||
|
||||
const testText = `test ${Math.random()}`
|
||||
await client2.sendText(username, testText)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(msgs.length).to.be.greaterThan(0)
|
||||
expect(msgs[0].text).to.equal(testText)
|
||||
})
|
||||
} catch (e) {
|
||||
await client2.close()
|
||||
throw e
|
||||
}
|
||||
|
||||
await client2.close()
|
||||
})
|
||||
|
||||
this.afterAll(async () => {
|
||||
await port.close()
|
||||
void worker.terminate()
|
||||
})
|
||||
})
|
|
@ -1,24 +0,0 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core/client.js'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
// @fix-import
|
||||
import { getApiParams } from '../../utils'
|
||||
|
||||
describe('@mtcute/core', function () {
|
||||
this.timeout(300_000)
|
||||
|
||||
it('connects to test DC and makes help.getNearestDc', async () => {
|
||||
const tg = new BaseTelegramClient({
|
||||
...getApiParams(),
|
||||
})
|
||||
|
||||
await tg.connect()
|
||||
const config = await tg.call({ _: 'help.getNearestDc' })
|
||||
await tg.close()
|
||||
|
||||
expect(config).to.be.an('object')
|
||||
expect(config._).to.equal('nearestDc')
|
||||
expect(config.thisDc).to.equal(2)
|
||||
})
|
||||
})
|
|
@ -1,115 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import { expect } from 'chai'
|
||||
import Long from 'long'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||
import { NodePlatform } from '@mtcute/node'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
|
||||
// here we primarily want to check that everything imports properly,
|
||||
// and that the code is actually executable. The actual correctness
|
||||
// of the implementation is covered tested by unit tests
|
||||
|
||||
const p = new NodePlatform()
|
||||
setPlatform(p)
|
||||
|
||||
describe('encodings', () => {
|
||||
it('works with Buffers', () => {
|
||||
expect(p.hexEncode(Buffer.from('hello'))).to.equal('68656c6c6f')
|
||||
expect(p.hexDecode('0102030405')).eql(Buffer.from([1, 2, 3, 4, 5]))
|
||||
})
|
||||
|
||||
it('works with Uint8Arrays', () => {
|
||||
expect(p.hexEncode(new Uint8Array([1, 2, 3, 4, 5]))).to.equal('0102030405')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TlBinaryReader', () => {
|
||||
const map = {
|
||||
'85337187': function (r: any) {
|
||||
const ret: any = {}
|
||||
ret._ = 'mt_resPQ'
|
||||
ret.nonce = r.int128()
|
||||
ret.serverNonce = r.int128()
|
||||
ret.pq = r.bytes()
|
||||
ret.serverPublicKeyFingerprints = r.vector(r.long)
|
||||
|
||||
return ret
|
||||
},
|
||||
}
|
||||
const data =
|
||||
'000000000000000001c8831ec97ae55140000000632416053e0549828cca27e966b301a48fece2fca5cf4d33f4a11ea877ba4aa5739073300817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3'
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
const buf = Buffer.from(data, 'hex')
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
expect(r.long().toString(16)).to.equal('51e57ac91e83c801')
|
||||
expect(r.uint()).to.equal(64)
|
||||
|
||||
const obj: any = r.object()
|
||||
expect(obj._).equal('mt_resPQ')
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
const buf = p.hexDecode(data)
|
||||
|
||||
const r = new TlBinaryReader(map, buf, 8)
|
||||
|
||||
expect(r.long().toString(16)).to.equal('51e57ac91e83c801')
|
||||
expect(r.uint()).to.equal(64)
|
||||
|
||||
const obj: any = r.object()
|
||||
expect(obj._).equal('mt_resPQ')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TlBinaryWriter', () => {
|
||||
const map = {
|
||||
mt_resPQ: function (w: any, obj: any) {
|
||||
w.uint(85337187)
|
||||
w.bytes(obj.pq)
|
||||
w.vector(w.long, obj.serverPublicKeyFingerprints)
|
||||
},
|
||||
_staticSize: {} as any,
|
||||
}
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: Buffer.from('17ED48941A08F981', 'hex'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
expect(TlSerializationCounter.countNeededBytes(map, obj)).to.equal(32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
expect(p.hexEncode(w.result())).eq(
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
const obj = {
|
||||
_: 'mt_resPQ',
|
||||
pq: p.hexDecode('17ED48941A08F981'),
|
||||
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||
}
|
||||
|
||||
expect(TlSerializationCounter.countNeededBytes(map, obj)).to.equal(32)
|
||||
|
||||
const w = TlBinaryWriter.alloc(map, 48)
|
||||
w.long(Long.ZERO)
|
||||
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||
w.object(obj)
|
||||
|
||||
expect(p.hexEncode(w.result())).eq(
|
||||
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,41 +0,0 @@
|
|||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import { NodePlatform } from '@mtcute/node'
|
||||
import { tl } from '@mtcute/tl'
|
||||
import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
|
||||
import { expect } from 'chai'
|
||||
import Long from 'long'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
// here we primarily want to check that @mtcute/tl correctly works with @mtcute/tl-runtime
|
||||
|
||||
const p = new NodePlatform()
|
||||
setPlatform(p)
|
||||
|
||||
describe('@mtcute/tl', () => {
|
||||
it('writers map works with TlBinaryWriter', () => {
|
||||
const obj = {
|
||||
_: 'inputPeerUser',
|
||||
userId: 123,
|
||||
accessHash: Long.fromNumber(456),
|
||||
}
|
||||
|
||||
expect(p.hexEncode(TlBinaryWriter.serializeObject(__tlWriterMap, obj))).to.equal(
|
||||
'4ca5e8dd7b00000000000000c801000000000000',
|
||||
)
|
||||
})
|
||||
|
||||
it('readers map works with TlBinaryReader', () => {
|
||||
const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000')
|
||||
const obj = TlBinaryReader.deserializeObject<any>(__tlReaderMap, buf)
|
||||
|
||||
expect(obj._).equal('inputPeerUser')
|
||||
expect(obj.userId).equal(123)
|
||||
expect(obj.accessHash.toString()).equal('456')
|
||||
})
|
||||
|
||||
it('correctly checks for combinator types', () => {
|
||||
expect(tl.isAnyInputUser({ _: 'inputUserEmpty' })).to.eq(true)
|
||||
})
|
||||
})
|
|
@ -1,30 +0,0 @@
|
|||
import { NodeCryptoProvider } from '@mtcute/node/utils.js'
|
||||
import { ige256Decrypt, ige256Encrypt } from '@mtcute/wasm'
|
||||
import { expect } from 'chai'
|
||||
import { before, describe, it } from 'mocha'
|
||||
|
||||
before(async () => {
|
||||
await new NodeCryptoProvider().initialize()
|
||||
})
|
||||
|
||||
describe('@mtcute/wasm', () => {
|
||||
const key = Buffer.from('5468697320697320616E20696D706C655468697320697320616E20696D706C65', 'hex')
|
||||
const iv = Buffer.from('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353', 'hex')
|
||||
|
||||
const data = Buffer.from('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b', 'hex')
|
||||
const dataEnc = Buffer.from('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69', 'hex')
|
||||
|
||||
it('should work with Buffers', () => {
|
||||
expect(ige256Encrypt(data, key, iv)).to.deep.equal(new Uint8Array(dataEnc))
|
||||
expect(ige256Decrypt(dataEnc, key, iv)).to.deep.equal(new Uint8Array(data))
|
||||
})
|
||||
|
||||
it('should work with Uint8Arrays', () => {
|
||||
expect(ige256Encrypt(new Uint8Array(data), new Uint8Array(key), new Uint8Array(iv))).to.deep.equal(
|
||||
new Uint8Array(dataEnc),
|
||||
)
|
||||
expect(ige256Decrypt(new Uint8Array(dataEnc), new Uint8Array(key), new Uint8Array(iv))).to.deep.equal(
|
||||
new Uint8Array(data),
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es2020",
|
||||
"rootDir": ".",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"outDir": "./dist",
|
||||
"sourceMap": true,
|
||||
"stripInternal": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"./tests",
|
||||
"./utils.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules"
|
||||
]
|
||||
}
|
30
e2e/package.json
Normal file
30
e2e/package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@mtcute/e2e-tests",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"fuman": {
|
||||
"private": true
|
||||
},
|
||||
"scripts": {
|
||||
"test": "dotenv tsx --trace-warnings --test --test-timeout=300000",
|
||||
"test:all": "find tests -type f -name '*.e2e.ts' -print0 | sort -z | xargs -0 -n1 pnpm run test",
|
||||
"deno:test": "deno test -A --unstable-ffi --trace-leaks --import-map=import-map.json --unstable-sloppy-imports --no-check --env-file=.env",
|
||||
"deno:test:all": "pnpm run deno:test tests/*.e2e.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fuman/utils": "0.0.1",
|
||||
"chai": "^4.3.10",
|
||||
"dotenv-cli": "7.4.4",
|
||||
"mtcute": "file:../packages/node",
|
||||
"tsx": "^4.19.2",
|
||||
"esbuild": "^0.24.0",
|
||||
"better-sqlite3": "11.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.8",
|
||||
"@types/mocha": "^10.0.2",
|
||||
"@types/node": "^20.8.10",
|
||||
"globstar": "1.0.0"
|
||||
}
|
||||
}
|
27
e2e/tests/01.auth.e2e.ts
Normal file
27
e2e/tests/01.auth.e2e.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { describe, it } from 'node:test'
|
||||
import { expect } from 'chai'
|
||||
import { TelegramClient, User } from 'mtcute'
|
||||
|
||||
import { getApiParams } from './_utils.js'
|
||||
|
||||
describe('1. authorization', () => {
|
||||
it('should authorize in default dc', async () => {
|
||||
const tg = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
await tg.importSession(process.env.SESSION_DC2!)
|
||||
|
||||
expect(await tg.getMe()).to.be.instanceOf(User)
|
||||
|
||||
await tg.close()
|
||||
})
|
||||
|
||||
it('should authorize in dc 1', async () => {
|
||||
const tg = new TelegramClient(getApiParams('dc1.session'))
|
||||
|
||||
await tg.importSession(process.env.SESSION_DC1!)
|
||||
|
||||
expect(await tg.getMe()).to.be.instanceOf(User)
|
||||
|
||||
await tg.close()
|
||||
})
|
||||
})
|
|
@ -1,16 +1,14 @@
|
|||
import { MtPeerNotFoundError } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { after, before, describe, it } from 'node:test'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
import { TelegramClient } from 'mtcute'
|
||||
|
||||
import { getApiParams } from '../utils.js'
|
||||
import { getApiParams } from './_utils.js'
|
||||
|
||||
describe('2. calling methods', function () {
|
||||
this.timeout(300_000)
|
||||
describe('2. calling methods', () => {
|
||||
const tg = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
this.beforeAll(() => tg.connect())
|
||||
this.afterAll(() => tg.close())
|
||||
before(() => tg.connect())
|
||||
after(() => tg.close())
|
||||
|
||||
it('getUsers(@BotFather)', async () => {
|
||||
const [user] = await tg.getUsers('botfather')
|
||||
|
@ -27,16 +25,7 @@ describe('2. calling methods', function () {
|
|||
})
|
||||
|
||||
it('getHistory(777000)', async () => {
|
||||
try {
|
||||
await tg.findDialogs(777000) // ensure it's cached
|
||||
} catch (e) {
|
||||
if (e instanceof MtPeerNotFoundError) {
|
||||
// this happens sometimes :D gracefully skip
|
||||
return
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
const history = await tg.getHistory(777000, { limit: 5 })
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import type { FileDownloadLocation } from '@mtcute/core'
|
||||
import type { FileDownloadLocation } from 'mtcute'
|
||||
|
||||
import { createHash } from 'node:crypto'
|
||||
import { Thumbnail } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import { sleep } from '@mtcute/core/utils.js'
|
||||
import { after, before, describe, it } from 'node:test'
|
||||
import { sleep } from '@fuman/utils'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
import { TelegramClient, Thumbnail } from 'mtcute'
|
||||
|
||||
import { getApiParams } from '../utils.js'
|
||||
import { getApiParams } from './_utils.js'
|
||||
|
||||
const CINNAMOROLL_PFP_CHAT = 'test_file_dc2'
|
||||
const CINNAMOROLL_PFP_THUMB_SHA256 = '3e6f220235a12547c16129f50c19ed3224d39b827414d1d500f79569a3431eae'
|
||||
|
@ -31,18 +30,15 @@ async function downloadAsSha256(client: TelegramClient, location: FileDownloadLo
|
|||
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('3. working with files', () => {
|
||||
describe('same-dc', () => {
|
||||
const tg = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
this.beforeAll(() => tg.connect())
|
||||
this.afterAll(() => tg.close())
|
||||
before(() => tg.connect())
|
||||
after(() => tg.close())
|
||||
|
||||
it('should download pfp thumbs', async () => {
|
||||
const chat = await tg.getChat(CINNAMOROLL_PFP_CHAT)
|
||||
|
@ -107,6 +103,7 @@ describe('3. working with files', function () {
|
|||
const promise = download()
|
||||
|
||||
// let it download for 10 seconds
|
||||
// file is 1gb so it is safe to assume it will take more than that to download
|
||||
await sleep(10000)
|
||||
abort.abort()
|
||||
// abort and snap the downloaded amount
|
||||
|
@ -127,8 +124,8 @@ describe('3. working with files', function () {
|
|||
describe('cross-dc', () => {
|
||||
const tg = new TelegramClient(getApiParams('dc1.session'))
|
||||
|
||||
this.beforeAll(() => tg.connect())
|
||||
this.afterAll(() => tg.close())
|
||||
before(() => tg.connect())
|
||||
after(() => tg.close())
|
||||
|
||||
it('should download pfp thumbs', async () => {
|
||||
const chat = await tg.getChat(CINNAMOROLL_PFP_CHAT)
|
|
@ -1,24 +1,23 @@
|
|||
import type { Message } from '@mtcute/core'
|
||||
import { TelegramClient } from '@mtcute/core/client.js'
|
||||
import type { Message } from 'mtcute'
|
||||
import { after, before, describe, it } from 'node:test'
|
||||
import { asNonNull } from '@fuman/utils'
|
||||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
|
||||
import { getApiParams, waitFor } from '../utils.js'
|
||||
|
||||
describe('4. handling updates', function () {
|
||||
this.timeout(300_000)
|
||||
import { TelegramClient } from 'mtcute'
|
||||
import { getApiParams, waitFor } from './_utils.js'
|
||||
|
||||
describe('4. handling updates', () => {
|
||||
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 () => {
|
||||
before(async () => {
|
||||
await tg1.connect()
|
||||
await tg1.startUpdatesLoop()
|
||||
await tg2.connect()
|
||||
})
|
||||
this.afterAll(async () => {
|
||||
after(async () => {
|
||||
await tg1.close()
|
||||
await tg2.close()
|
||||
})
|
||||
|
@ -26,15 +25,10 @@ describe('4. handling updates', function () {
|
|||
it('should send and receive messages', async () => {
|
||||
const tg1Messages: Message[] = []
|
||||
|
||||
tg1.on('new_message', msg => tg1Messages.push(msg))
|
||||
tg1.onNewMessage.add(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 username = asNonNull(tg1User!.username)
|
||||
|
||||
const messageText = `mtcute test message ${Math.random().toString(36).slice(2)}`
|
||||
const sentMsg = await tg2.sendText(username, messageText)
|
127
e2e/tests/05.worker.e2e.ts
Normal file
127
e2e/tests/05.worker.e2e.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import type { Message } from 'mtcute'
|
||||
|
||||
import type { Worker as NodeWorker } from 'node:worker_threads'
|
||||
import type { CustomMethods } from './_worker.js'
|
||||
import { writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { after, describe, it } from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { asNonNull } from '@fuman/utils'
|
||||
import { expect } from 'chai'
|
||||
import { build } from 'esbuild'
|
||||
import { Long, TelegramClient, TelegramWorkerPort, tl } from 'mtcute'
|
||||
import { getApiParams, IS_BUN, IS_DENO, IS_NODE, RUNTIME_DIR, waitFor } from './_utils.js'
|
||||
|
||||
const workerFile = new URL('./_worker.ts', import.meta.url)
|
||||
let worker: Worker | NodeWorker
|
||||
if (IS_NODE) {
|
||||
// because of https://github.com/privatenumber/tsx/issues/354 we can't use tsx directly lol
|
||||
const compiled = (await build({
|
||||
entryPoints: [fileURLToPath(workerFile)],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
write: false,
|
||||
target: 'esnext',
|
||||
platform: 'node',
|
||||
// NB: we have better-sqlite3 in deps to this package because otherwise node fails to resolve it
|
||||
external: ['node:*', 'better-sqlite3'],
|
||||
})).outputFiles[0].text
|
||||
const compiledWorkerFile = join(RUNTIME_DIR, '_worker.js')
|
||||
await writeFile(compiledWorkerFile, compiled)
|
||||
|
||||
const { Worker } = await import('node:worker_threads')
|
||||
worker = new Worker(compiledWorkerFile)
|
||||
worker.on('exit', (code) => {
|
||||
console.error('worker exited with code', code)
|
||||
})
|
||||
|
||||
worker.on('error', (err) => {
|
||||
console.error('worker error', err)
|
||||
process.exit(1)
|
||||
})
|
||||
} else if (IS_BUN) {
|
||||
const { Worker } = await import('node:worker_threads')
|
||||
worker = new Worker(workerFile)
|
||||
worker.on('exit', (code) => {
|
||||
console.error('worker exited with code', code)
|
||||
})
|
||||
|
||||
worker.on('error', (err) => {
|
||||
console.error('worker error', err)
|
||||
process.exit(1)
|
||||
})
|
||||
} else if (IS_DENO) {
|
||||
worker = new Worker(workerFile, { type: 'module' })
|
||||
}
|
||||
|
||||
describe('5. worker', () => {
|
||||
const port = new TelegramWorkerPort<CustomMethods>({
|
||||
worker,
|
||||
})
|
||||
const portClient = new TelegramClient({ client: port })
|
||||
|
||||
it('should make api calls', async () => {
|
||||
const res = await port.call({ _: 'help.getConfig' })
|
||||
expect(res._).to.equal('config')
|
||||
|
||||
const premiumPromo = await port.call({ _: 'help.getPremiumPromo' })
|
||||
// ensure Long-s are correctly serialized
|
||||
expect(Long.isLong((premiumPromo.users[0] as tl.RawUser).accessHash)).to.equal(true)
|
||||
})
|
||||
|
||||
it('should call custom methods', async () => {
|
||||
const hello = await port.invokeCustom('hello')
|
||||
expect(hello).to.equal('world')
|
||||
|
||||
const sum = await port.invokeCustom('sum', 2, 3)
|
||||
expect(sum).to.equal(5)
|
||||
})
|
||||
|
||||
it('should throw errors', async () => {
|
||||
try {
|
||||
await port.call({ _: 'test.useConfigSimple' })
|
||||
throw new Error('should have thrown')
|
||||
} catch (e) {
|
||||
expect(e).to.be.an.instanceOf(tl.RpcError)
|
||||
}
|
||||
})
|
||||
|
||||
it('should receive updates', async () => {
|
||||
const client2 = new TelegramClient(getApiParams('dc2.session'))
|
||||
|
||||
try {
|
||||
await client2.connect()
|
||||
await port.startUpdatesLoop()
|
||||
|
||||
const me = await portClient.getMe()
|
||||
// ensure Long-s are correctly serialized
|
||||
expect(Long.isLong(me.raw.accessHash)).equals(true)
|
||||
|
||||
const username = asNonNull(me.username)
|
||||
|
||||
const msgs: Message[] = []
|
||||
portClient.onNewMessage.add((msg) => {
|
||||
msgs.push(msg)
|
||||
})
|
||||
|
||||
const testText = `mtcute worker test ${Math.random()}`
|
||||
await client2.sendText(username, testText)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(msgs.length).to.be.greaterThan(0)
|
||||
expect(msgs[0].text).to.equal(testText)
|
||||
})
|
||||
} catch (e) {
|
||||
await client2.close()
|
||||
throw e
|
||||
}
|
||||
|
||||
await client2.close()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await port.close()
|
||||
void worker.terminate()
|
||||
})
|
||||
})
|
|
@ -1,28 +1,27 @@
|
|||
import type { MaybePromise } from '@mtcute/core'
|
||||
import type { BaseTelegramClientOptions, MaybePromise } from 'mtcute'
|
||||
|
||||
import type { BaseTelegramClientOptions } from '@mtcute/core/client.js'
|
||||
import { join } from 'node:path'
|
||||
import { MemoryStorage } from '@mtcute/core'
|
||||
import { setPlatform } from '@mtcute/core/platform.js'
|
||||
import { LogManager, sleep } from '@mtcute/core/utils.js'
|
||||
import { NodePlatform, SqliteStorage, TcpTransport } from '@mtcute/node'
|
||||
import { NodeCryptoProvider } from '@mtcute/node/utils.js'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { sleep } from '@fuman/utils'
|
||||
import { MemoryStorage, SqliteStorage } from 'mtcute'
|
||||
import { LogManager } from 'mtcute/utils.js'
|
||||
|
||||
export const RUNTIME_DIR: string = fileURLToPath(new URL('../_runtime', import.meta.url))
|
||||
export const IS_DENO = 'Deno' in globalThis
|
||||
export const IS_BUN = 'Bun' in globalThis
|
||||
export const IS_NODE = !IS_DENO && !IS_BUN
|
||||
|
||||
export function 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')
|
||||
}
|
||||
|
||||
setPlatform(new NodePlatform())
|
||||
|
||||
return {
|
||||
apiId: Number.parseInt(process.env.API_ID),
|
||||
apiHash: process.env.API_HASH,
|
||||
testMode: true,
|
||||
storage: storage ? new SqliteStorage(join(__dirname, storage)) : new MemoryStorage(),
|
||||
storage: storage ? new SqliteStorage(join(RUNTIME_DIR, storage)) : new MemoryStorage(),
|
||||
logLevel: LogManager.VERBOSE,
|
||||
transport: () => new TcpTransport(),
|
||||
crypto: new NodeCryptoProvider(),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import type { WorkerCustomMethods } from '@mtcute/core/worker.js'
|
||||
import { BaseTelegramClient, TelegramWorker } from '@mtcute/node'
|
||||
|
||||
import { getApiParams } from '../utils.js'
|
||||
import type { WorkerCustomMethods } from 'mtcute'
|
||||
import { BaseTelegramClient, TelegramWorker } from 'mtcute'
|
||||
import { getApiParams } from './_utils.ts'
|
||||
|
||||
const customMethods = {
|
||||
hello: async () => 'world',
|
17
e2e/tests/deno-shims/node-test.js
Normal file
17
e2e/tests/deno-shims/node-test.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import * as bdd from 'jsr:@std/testing@1.0.5/bdd'
|
||||
|
||||
export function describe(name, opts, fn) {
|
||||
if (typeof opts === 'function') {
|
||||
fn = opts
|
||||
opts = {}
|
||||
}
|
||||
|
||||
bdd.describe(name, {
|
||||
...opts,
|
||||
// we don't close @db/sqlite
|
||||
sanitizeResources: false,
|
||||
}, fn)
|
||||
}
|
||||
export const it = bdd.it
|
||||
export const before = bdd.beforeAll
|
||||
export const after = bdd.afterAll
|
5
e2e/tests/deno-shims/tl-reader.js
Normal file
5
e2e/tests/deno-shims/tl-reader.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export const { __tlReaderMap } = require('../../../packages/tl/binary/reader.js')
|
5
e2e/tests/deno-shims/tl-rsa.js
Normal file
5
e2e/tests/deno-shims/tl-rsa.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export const { __publicKeyIndex } = require('../../../packages/tl/binary/rsa-keys.js')
|
5
e2e/tests/deno-shims/tl-writer.js
Normal file
5
e2e/tests/deno-shims/tl-writer.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export const { __tlWriterMap } = require('../../../packages/tl/binary/writer.js')
|
5
e2e/tests/deno-shims/tl.js
Normal file
5
e2e/tests/deno-shims/tl.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createRequire } from 'node:module'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export const { tl, mtp } = require('../../../packages/tl/index.js')
|
10
e2e/tsconfig.json
Normal file
10
e2e/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedDeclarations": false
|
||||
},
|
||||
"include": [
|
||||
"./tests"
|
||||
]
|
||||
}
|
|
@ -2,6 +2,7 @@ import antfu from '@antfu/eslint-config'
|
|||
|
||||
export default antfu({
|
||||
type: 'lib',
|
||||
ignores: ['e2e/runtime/*'],
|
||||
typescript: process.env.CI
|
||||
? {
|
||||
tsconfigPath: 'tsconfig.json',
|
||||
|
|
644
pnpm-lock.yaml
644
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,3 @@
|
|||
packages:
|
||||
- packages/*
|
||||
- '!e2e/*'
|
||||
- e2e
|
|
@ -32,7 +32,6 @@
|
|||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": [
|
||||
"e2e/",
|
||||
"**/node_modules",
|
||||
"**/private",
|
||||
"**/__snapshots__"
|
||||
|
|
Loading…
Reference in a new issue