test: use instant accounts instead of mailadm

This commit is contained in:
link2xt
2023-10-17 21:26:04 +00:00
parent 1d80659bc3
commit 6aae0276da
16 changed files with 101 additions and 94 deletions

View File

@@ -211,7 +211,7 @@ jobs:
- name: Run python tests - name: Run python tests
env: env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }} CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
DCC_RS_TARGET: debug DCC_RS_TARGET: debug
DCC_RS_DEV: ${{ github.workspace }} DCC_RS_DEV: ${{ github.workspace }}
working-directory: python working-directory: python
@@ -274,6 +274,6 @@ jobs:
- name: Run deltachat-rpc-client tests - name: Run deltachat-rpc-client tests
env: env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }} CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
working-directory: deltachat-rpc-client working-directory: deltachat-rpc-client
run: tox -e py run: tox -e py

View File

@@ -31,7 +31,7 @@ jobs:
working-directory: deltachat-jsonrpc/typescript working-directory: deltachat-jsonrpc/typescript
run: npm run test run: npm run test
env: env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }} CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
- name: make sure websocket server version still builds - name: make sure websocket server version still builds
working-directory: deltachat-jsonrpc working-directory: deltachat-jsonrpc
run: cargo build --bin deltachat-jsonrpc-server --features webserver run: cargo build --bin deltachat-jsonrpc-server --features webserver

View File

@@ -63,5 +63,5 @@ jobs:
working-directory: node working-directory: node
run: npm run test run: npm run test
env: env:
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }} CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true" NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"

View File

@@ -108,10 +108,10 @@ This will build the `deltachat-jsonrpc-server` binary and then run a test suite
The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, talk to DeltaChat developers to get a token for the `testrun.org` service, or use a local instance of [`mailadm`](https://github.com/deltachat/docker-mailadm). The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, talk to DeltaChat developers to get a token for the `testrun.org` service, or use a local instance of [`mailadm`](https://github.com/deltachat/docker-mailadm).
Then, set the `DCC_NEW_TMP_EMAIL` environment variable to your mailadm token before running the tests. Then, set the `CHATMAIL_DOMAIN` environment variable to your testing email server domain.
``` ```
DCC_NEW_TMP_EMAIL=https://testrun.org/new_email?t=yourtoken npm run test CHATMAIL_DOMAIN=chat.example.org npm run test
``` ```
#### Test Coverage #### Test Coverage

View File

@@ -13,16 +13,16 @@ describe("online tests", function () {
before(async function () { before(async function () {
this.timeout(60000); this.timeout(60000);
if (!process.env.DCC_NEW_TMP_EMAIL) { if (!process.env.CHATMAIL_DOMAIN) {
if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) { if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) {
console.error( console.error(
"CAN NOT RUN COVERAGE correctly: Missing DCC_NEW_TMP_EMAIL environment variable!\n\n", "CAN NOT RUN COVERAGE correctly: Missing CHATMAIL_DOMAIN environment variable!\n\n",
"You can set COVERAGE_OFFLINE=1 to circumvent this check and skip the online tests, but those coverage results will be wrong, because some functions can only be tested in the online test" "You can set COVERAGE_OFFLINE=1 to circumvent this check and skip the online tests, but those coverage results will be wrong, because some functions can only be tested in the online test"
); );
process.exit(1); process.exit(1);
} }
console.log( console.log(
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests" "Missing CHATMAIL_DOMAIN environment variable!, skip integration tests"
); );
this.skip(); this.skip();
} }
@@ -33,7 +33,7 @@ describe("online tests", function () {
if (kind !== "Info") console.log(contextId, kind); if (kind !== "Info") console.log(contextId, kind);
}); });
account1 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL); account1 = createTempUser(process.env.CHATMAIL_DOMAIN);
if (!account1 || !account1.email || !account1.password) { if (!account1 || !account1.email || !account1.password) {
console.log( console.log(
"We didn't got back an account from the api, skip integration tests" "We didn't got back an account from the api, skip integration tests"
@@ -41,7 +41,7 @@ describe("online tests", function () {
this.skip(); this.skip();
} }
account2 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL); account2 = createTempUser(process.env.CHATMAIL_DOMAIN);
if (!account2 || !account2.email || !account2.password) { if (!account2 || !account2.email || !account2.password) {
console.log( console.log(
"We didn't got back an account2 from the api, skip integration tests" "We didn't got back an account2 from the api, skip integration tests"

View File

@@ -57,15 +57,14 @@ export async function startServer(): Promise<RpcServerHandle> {
}; };
} }
export async function createTempUser(url: string) { export function createTempUser(chatmailDomain: String) {
const response = await fetch(url, { const charset = "2345789acdefghjkmnpqrstuvwxyz";
method: "POST", let user = "ci-";
headers: { for (let i = 0; i < 6; i++) {
"cache-control": "no-cache", user += charset[Math.floor(Math.random() * charset.length)];
}, }
}); const email = user + "@" + chatmailDomain;
if (!response.ok) throw new Error("Received invalid response"); return { email: email, password: user + "$" + user };
return response.json();
} }
function getTargetDir(): Promise<string> { function getTargetDir(): Promise<string> {

View File

@@ -1,6 +1,5 @@
import json
import os import os
import urllib.request import random
from typing import AsyncGenerator, List, Optional from typing import AsyncGenerator, List, Optional
import pytest import pytest
@@ -10,12 +9,11 @@ from .rpc import Rpc
def get_temp_credentials() -> dict: def get_temp_credentials() -> dict:
url = os.getenv("DCC_NEW_TMP_EMAIL") domain = os.getenv("CHATMAIL_DOMAIN")
assert url, "Failed to get online account, DCC_NEW_TMP_EMAIL is not set" username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6))
password = f"{username}${username}"
request = urllib.request.Request(url, method="POST") addr = f"{username}@{domain}"
with urllib.request.urlopen(request, timeout=60) as f: return {"email": addr, "password": password}
return json.load(f)
class ACFactory: class ACFactory:

View File

@@ -11,7 +11,7 @@ setenv =
# Avoid stack overflow when Rust core is built without optimizations. # Avoid stack overflow when Rust core is built without optimizations.
RUST_MIN_STACK=8388608 RUST_MIN_STACK=8388608
passenv = passenv =
DCC_NEW_TMP_EMAIL CHATMAIL_DOMAIN
deps = deps =
pytest pytest
pytest-timeout pytest-timeout

View File

@@ -204,10 +204,10 @@ Running `npm test` ends with showing a code coverage report, which is produced b
The coverage report from `nyc` in the console is rather limited. To get a more detailed coverage report you can run `npm run coverage-html-report`. This will produce a html report from the `nyc` data and display it in a browser on your local machine. The coverage report from `nyc` in the console is rather limited. To get a more detailed coverage report you can run `npm run coverage-html-report`. This will produce a html report from the `nyc` data and display it in a browser on your local machine.
To run the integration tests you need to set the `DCC_NEW_TMP_EMAIL` environment variables. E.g.: To run the integration tests you need to set the `CHATMAIL_DOMAIN` environment variables. E.g.:
``` ```
$ export DCC_NEW_TMP_EMAIL=https://testrun.org/new_email?t=[token] $ export CHATMAIL_DOMAIN=chat.example.org
$ npm run test $ npm run test
``` ```

View File

@@ -12,22 +12,14 @@ import fetch from 'node-fetch'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
chai.config.truncateThreshold = 0 // Do not truncate assertion errors. chai.config.truncateThreshold = 0 // Do not truncate assertion errors.
async function createTempUser(url) { function createTempUser(chatmailDomain) {
async function postData(url = '') { const charset = "2345789acdefghjkmnpqrstuvwxyz";
// Default options are marked with * let user = "ci-";
const response = await fetch(url, { for (let i = 0; i < 6; i++) {
method: 'POST', // *GET, POST, PUT, DELETE, etc. user += charset[Math.floor(Math.random() * charset.length)];
headers: {
'cache-control': 'no-cache',
},
})
if (!response.ok) {
throw new Error('request failed: ' + response.body.read())
}
return response.json() // parses JSON response into native JavaScript objects
} }
const email = user + "@" + chatmailDomain;
return await postData(url) return { email: email, password: user + "$" + user };
} }
describe('static tests', function () { describe('static tests', function () {
@@ -768,14 +760,7 @@ describe('Integration tests', function () {
}) })
this.beforeAll(async function () { this.beforeAll(async function () {
if (!process.env.DCC_NEW_TMP_EMAIL) { account = createTempUser(process.env.CHATMAIL_DOMAIN)
console.log(
'Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests'
)
this.skip()
}
account = await createTempUser(process.env.DCC_NEW_TMP_EMAIL)
if (!account || !account.email || !account.password) { if (!account || !account.email || !account.password) {
console.log( console.log(
"We didn't got back an account from the api, skip integration tests" "We didn't got back an account from the api, skip integration tests"

View File

@@ -53,14 +53,14 @@ end-to-end tests that require accounts on real e-mail servers.
Running "live" tests with temporary accounts Running "live" tests with temporary accounts
-------------------------------------------- --------------------------------------------
If you want to run live functional tests you can set ``DCC_NEW_TMP_EMAIL`` to a URL that creates e-mail accounts. Most developers use https://testrun.org URLs created and managed by `mailadm <https://mailadm.readthedocs.io/>`_. If you want to run live functional tests
you can set ``CHATMAIL_DOMAIN`` to a domain of the email server
that creates e-mail accounts like this::
Please feel free to contact us through a github issue or by e-mail and we'll send you a URL that you can then use for functional tests like this:: export CHATMAIL_DOMAIN=nine.testrun.org
export DCC_NEW_TMP_EMAIL=<URL you got from us> With this account-creation setting, pytest runs create ephemeral e-mail accounts on the server.
These accounts have the pattern `ci-{6 characters}@{CHATMAIL_DOMAIN}`.
With this account-creation setting, pytest runs create ephemeral e-mail accounts on the http://testrun.org server.
These accounts are removed automatically as they expire.
After setting the variable, either rerun `scripts/run-python-test.sh` After setting the variable, either rerun `scripts/run-python-test.sh`
or run offline and online tests with `tox` directly:: or run offline and online tests with `tox` directly::
@@ -159,6 +159,6 @@ find it with::
This docker image can be used to run tests and build Python wheels for all interpreters:: This docker image can be used to run tests and build Python wheels for all interpreters::
$ docker run -e DCC_NEW_TMP_EMAIL \ $ docker run -e CHATMAIL_DOMAIN \
--rm -it -v $(pwd):/mnt -w /mnt \ --rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps scripts/run_all.sh deltachat/coredeps scripts/run_all.sh

View File

@@ -8,11 +8,11 @@ import sys
import threading import threading
import time import time
import weakref import weakref
import random
from queue import Queue from queue import Queue
from typing import Callable, Dict, List, Optional, Set from typing import Callable, Dict, List, Optional, Set
import pytest import pytest
import requests
from _pytest._code import Source from _pytest._code import Source
import deltachat import deltachat
@@ -29,6 +29,12 @@ def pytest_addoption(parser):
default=None, default=None,
help="a file with >=2 lines where each line contains NAME=VALUE config settings for one account", help="a file with >=2 lines where each line contains NAME=VALUE config settings for one account",
) )
group.addoption(
"--chatmail",
action="store",
default=None,
help="chatmail server domain name",
)
group.addoption( group.addoption(
"--ignored", "--ignored",
action="store_true", action="store_true",
@@ -53,10 +59,12 @@ def pytest_addoption(parser):
def pytest_configure(config): def pytest_configure(config):
cfg = config.getoption("--liveconfig") cfg = config.getoption("--liveconfig")
cfg = config.getoption("--chatmail")
if not cfg: if not cfg:
cfg = os.getenv("DCC_NEW_TMP_EMAIL") cfg = os.getenv("CHATMAIL_DOMAIN")
if cfg: if cfg:
config.option.liveconfig = cfg config.option.chatmail = cfg
# Make sure we don't get garbled output because threads keep running # Make sure we don't get garbled output because threads keep running
# collect all ever created accounts in a weakref-set (so we don't # collect all ever created accounts in a weakref-set (so we don't
@@ -157,13 +165,29 @@ class TestProcess:
"""provide live account configs, cached on a per-test-process scope """provide live account configs, cached on a per-test-process scope
so that test functions can re-use already known live configs. so that test functions can re-use already known live configs.
Depending on the --liveconfig option this comes from Depending on the --liveconfig option this comes from
a HTTP provider or a file with a line specifying each accounts config. a file with a line specifying each accounts config
or a --chatmail domain.
""" """
liveconfig_opt = self.pytestconfig.getoption("--liveconfig") liveconfig_opt = self.pytestconfig.getoption("--liveconfig")
if not liveconfig_opt: chatmail_opt = self.pytestconfig.getoption("--chatmail")
pytest.skip("specify DCC_NEW_TMP_EMAIL or --liveconfig to provide live accounts") if chatmail_opt:
# Use a chatmail instance.
if not liveconfig_opt.startswith("http"): domain = chatmail_opt
MAX_LIVE_CREATED_ACCOUNTS = 10
for index in range(MAX_LIVE_CREATED_ACCOUNTS):
try:
yield self._configlist[index]
except IndexError:
username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6))
password = f"{username}${username}"
addr = f"{username}@{domain}"
config = {"addr": addr, "mail_pw": password}
print("newtmpuser {}: addr={}".format(index, config["addr"]))
self._configlist.append(config)
yield config
pytest.fail(f"more than {MAX_LIVE_CREATED_ACCOUNTS} live accounts requested.")
elif liveconfig_opt:
# Read a list of accounts from file.
for line in open(liveconfig_opt): for line in open(liveconfig_opt):
if line.strip() and not line.strip().startswith("#"): if line.strip() and not line.strip().startswith("#"):
d = {} d = {}
@@ -174,20 +198,9 @@ class TestProcess:
yield from iter(self._configlist) yield from iter(self._configlist)
else: else:
MAX_LIVE_CREATED_ACCOUNTS = 10 pytest.skip(
for index in range(MAX_LIVE_CREATED_ACCOUNTS): "specify --liveconfig, CHATMAIL_DOMAIN or --chatmail to provide live accounts",
try: )
yield self._configlist[index]
except IndexError:
res = requests.post(liveconfig_opt, timeout=60)
if res.status_code != 200:
pytest.fail(f"newtmpuser count={index} code={res.status_code}: '{res.text}'")
d = res.json()
config = {"addr": d["email"], "mail_pw": d["password"]}
print("newtmpuser {}: addr={}".format(index, config["addr"]))
self._configlist.append(config)
yield config
pytest.fail(f"more than {MAX_LIVE_CREATED_ACCOUNTS} live accounts requested.")
def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path): def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path):
db_target_path = pathlib.Path(db_target_path) db_target_path = pathlib.Path(db_target_path)

View File

@@ -2208,7 +2208,17 @@ def test_delete_multiple_messages(acfactory, lp):
def test_trash_multiple_messages(acfactory, lp): def test_trash_multiple_messages(acfactory, lp):
ac1, ac2 = acfactory.get_online_accounts(2) ac1, ac2 = acfactory.get_online_accounts(2)
# Create the Trash folder on IMAP server
# and recreate the account so Trash folder is configured.
lp.sec("Creating trash folder")
ac2.direct_imap.create_folder("Trash")
lp.sec("Creating new accounts")
ac2 = acfactory.new_online_configuring_account(cloned_from=ac2)
acfactory.bring_accounts_online()
ac2.set_config("delete_to_trash", "1") ac2.set_config("delete_to_trash", "1")
assert ac2.get_config("configured_trash_folder")
chat12 = acfactory.get_accepted_chat(ac1, ac2) chat12 = acfactory.get_accepted_chat(ac1, ac2)
lp.sec("ac1: sending 3 messages") lp.sec("ac1: sending 3 messages")
@@ -2239,13 +2249,15 @@ def test_trash_multiple_messages(acfactory, lp):
def test_configure_error_msgs_wrong_pw(acfactory): def test_configure_error_msgs_wrong_pw(acfactory):
configdict = acfactory.get_next_liveconfig() (ac1,) = acfactory.get_online_accounts(1)
ac1 = acfactory.get_unconfigured_account()
ac1.update_config(configdict) ac2 = acfactory.get_unconfigured_account()
ac1.set_config("mail_pw", "abc") # Wrong mail pw ac2.set_config("addr", ac1.get_config("addr"))
ac1.configure() ac2.set_config("mail_pw", "abc") # Wrong mail pw
ac2.configure()
while True: while True:
ev = ac1._evtracker.get_matching("DC_EVENT_CONFIGURE_PROGRESS") ev = ac2._evtracker.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
print(f"Configuration progress: {ev.data1}")
if ev.data1 == 0: if ev.data1 == 0:
break break
# Password is wrong so it definitely has to say something about "password" # Password is wrong so it definitely has to say something about "password"
@@ -2548,7 +2560,7 @@ class TestOnlineConfigureFails:
def test_invalid_user(self, acfactory): def test_invalid_user(self, acfactory):
configdict = acfactory.get_next_liveconfig() configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account() ac1 = acfactory.get_unconfigured_account()
configdict["addr"] = "x" + configdict["addr"] configdict["addr"] = "$" + configdict["addr"]
ac1.update_config(configdict) ac1.update_config(configdict)
configtracker = ac1.configure() configtracker = ac1.configure()
configtracker.wait_progress(500) configtracker.wait_progress(500)
@@ -2557,7 +2569,7 @@ class TestOnlineConfigureFails:
def test_invalid_domain(self, acfactory): def test_invalid_domain(self, acfactory):
configdict = acfactory.get_next_liveconfig() configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account() ac1 = acfactory.get_unconfigured_account()
configdict["addr"] += "x" configdict["addr"] += "$"
ac1.update_config(configdict) ac1.update_config(configdict)
configtracker = ac1.configure() configtracker = ac1.configure()
configtracker.wait_progress(500) configtracker.wait_progress(500)

View File

@@ -16,7 +16,7 @@ setenv =
passenv = passenv =
DCC_RS_DEV DCC_RS_DEV
DCC_RS_TARGET DCC_RS_TARGET
DCC_NEW_TMP_EMAIL CHATMAIL_DOMAIN
CARGO_TARGET_DIR CARGO_TARGET_DIR
RUSTC_WRAPPER RUSTC_WRAPPER
deps = deps =

View File

@@ -28,7 +28,7 @@ ssh $SSHTARGET <<_HERE
export RUSTC_WRAPPER=\`which sccache\` export RUSTC_WRAPPER=\`which sccache\`
cd $BUILDDIR cd $BUILDDIR
export TARGET=release export TARGET=release
export DCC_NEW_TMP_EMAIL=$DCC_NEW_TMP_EMAIL export CHATMAIL_DOMAIN=$CHATMAIL_DOMAIN
#we rely on tox/virtualenv being available in the host #we rely on tox/virtualenv being available in the host
#rm -rf virtualenv venv #rm -rf virtualenv venv

View File

@@ -27,7 +27,7 @@ mkdir -p $TOXWORKDIR
# XXX we may switch on some live-tests on for better ensurances # XXX we may switch on some live-tests on for better ensurances
# Note that the independent remote_tests_python step does all kinds of # Note that the independent remote_tests_python step does all kinds of
# live-testing already. # live-testing already.
unset DCC_NEW_TMP_EMAIL unset CHATMAIL_DOMAIN
# Try to build wheels for a range of interpreters, but don't fail if they are not available. # Try to build wheels for a range of interpreters, but don't fail if they are not available.
# E.g. musllinux_1_1 does not have PyPy interpreters as of 2022-07-10 # E.g. musllinux_1_1 does not have PyPy interpreters as of 2022-07-10