Compare commits

..

14 Commits

Author SHA1 Message Date
B. Petersen
f7c90d626e adapt tests 2023-11-01 21:54:41 +01:00
B. Petersen
67b35b1baa add icon-group 2023-11-01 21:13:54 +01:00
link2xt
6aae0276da test: use instant accounts instead of mailadm 2023-10-30 00:15:22 +00:00
link2xt
1d80659bc3 chore: update portable-atomic dependency
Version 1.5.0 is yanked.
2023-10-29 23:36:26 +00:00
link2xt
94d5e86d4f refactor: rename repl_msg_by_error into replace_msg_by_error
This function has been named like this since it was a C function.
`repl` is unclear because it may stand for `reply`
as well as `replace`.
2023-10-29 17:09:59 +00:00
link2xt
aecf7729d8 chore(release): prepare for 1.127.2 2023-10-29 16:29:04 +00:00
Simon Laux
f130d537b7 api(jsonrpc): add get_message_info_object 2023-10-29 16:26:01 +00:00
Ajay Gonepuri
f30f862e7e docs: fix typos 2023-10-29 13:13:44 +00:00
link2xt
81e1164358 test: compile deltachat-rpc-server in debug mode for tests
This reduces compilation times.
2023-10-29 01:09:35 +00:00
Simon Laux
542bd4cbb8 api!: jsonrpc misc_set_draft now requires setting the viewtype 2023-10-28 08:42:14 +00:00
link2xt
771b57778e test: increase pytest timeout to 10 minutes
deltachat-rpc-client tests often timeouts on CI,
this hopefully fixes the problem unless the tests actually deadlock.
2023-10-28 00:54:36 +00:00
link2xt
9be56a5e56 test(deltachat-rpc-client): test securejoin 2023-10-27 20:07:32 +00:00
link2xt
da744958c2 chore: move pytest option from pyproject.toml to tox.ini and set log level
The option from pyproject.toml was not picked up
because tox.ini has higher precedence.
2023-10-27 20:00:20 +00:00
link2xt
f6bda1e480 docs: add missing 1.127.1 link to changelog 2023-10-27 19:34:49 +00:00
42 changed files with 406 additions and 178 deletions

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,19 @@
# Changelog
## [1.127.2] - 2023-10-29
### API-Changes
- [**breaking**] Jsonrpc `misc_set_draft` now requires setting the viewtype.
- jsonrpc: Add `get_message_info_object`.
### Tests
- deltachat-rpc-client: Move pytest option from pyproject.toml to tox.ini and set log level.
- deltachat-rpc-client: Test securejoin.
- Increase pytest timeout to 10 minutes.
- Compile deltachat-rpc-server in debug mode for tests.
## [1.127.1] - 2023-10-27
### API-Changes
@@ -1078,7 +1092,7 @@ Bugfix release attempting to fix the [iOS build error](https://github.com/deltac
### Changes
- Look at Authentication-Results. Don't accept Autocrypt key changes
if they come with negative authentiation results while this contact
if they come with negative authentication results while this contact
sent emails with positive authentication results in the past. #3583
- jsonrpc in cffi also sends events now #3662
- jsonrpc: new format for events and better typescript autocompletion
@@ -2664,7 +2678,7 @@ Bugfix release attempting to fix the [iOS build error](https://github.com/deltac
- delete all consumed secure-join handshake messagess #1209 #1212
- rust-level cleanups #1218 #1217 #1210 #1205
- Rust-level cleanups #1218 #1217 #1210 #1205
- python-level cleanups #1204 #1202 #1201
@@ -3033,3 +3047,5 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.126.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.125.0...v1.126.0
[1.126.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.126.0...v1.126.1
[1.127.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.126.1...v1.127.0
[1.127.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.127.0...v1.127.1
[1.127.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.127.1...v1.127.2

14
Cargo.lock generated
View File

@@ -1087,7 +1087,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.127.1"
version = "1.127.2"
dependencies = [
"ansi_term",
"anyhow",
@@ -1165,7 +1165,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.127.1"
version = "1.127.2"
dependencies = [
"anyhow",
"async-channel 2.0.0",
@@ -1189,7 +1189,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.127.1"
version = "1.127.2"
dependencies = [
"ansi_term",
"anyhow",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.127.1"
version = "1.127.2"
dependencies = [
"anyhow",
"deltachat",
@@ -1229,7 +1229,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.127.1"
version = "1.127.2"
dependencies = [
"anyhow",
"deltachat",
@@ -3438,9 +3438,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b559898e0b4931ed2d3b959ab0c2da4d99cc644c4b0b1a35b4d344027f474023"
checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b"
[[package]]
name = "postcard"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.127.1"
version = "1.127.2"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.70"

BIN
assets/icon-group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

68
assets/icon-group.svg Normal file
View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0beta1 (32d4812, 2019-09-19)"
sodipodi:docname="icon-group.svg"
id="svg835"
version="1.1"
viewBox="0 0 60 60"
height="60"
width="60"
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-group.png"
inkscape:export-xdpi="409.60001"
inkscape:export-ydpi="409.60001">
<metadata
id="metadata841">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs839" />
<sodipodi:namedview
inkscape:current-layer="svg835"
inkscape:window-maximized="0"
inkscape:window-y="23"
inkscape:window-x="46"
inkscape:cy="32.923751"
inkscape:cx="17.224772"
inkscape:zoom="9.9662545"
showgrid="false"
id="namedview837"
inkscape:window-height="1035"
inkscape:window-width="1679"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
inkscape:document-rotation="0"
bordercolor="#666666"
pagecolor="#ffffff" />
<rect
style="fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.738492;stroke-miterlimit:4;stroke-dasharray:none"
id="rect838"
width="60"
height="60"
x="0"
y="0" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.74233"
id="path833"
fill="none"
d="m 36.969303,28.257673 c 2.892261,0 5.209554,-2.334717 5.209554,-5.226978 0,-2.892262 -2.317293,-5.226978 -5.209554,-5.226978 -2.892261,0 -5.226978,2.334716 -5.226978,5.226978 0,2.892261 2.334717,5.226978 5.226978,5.226978 z m -13.938606,0 c 2.892261,0 5.209555,-2.334717 5.209555,-5.226978 0,-2.892262 -2.317294,-5.226978 -5.209555,-5.226978 -2.892261,0 -5.226978,2.334716 -5.226978,5.226978 0,2.892261 2.334717,5.226978 5.226978,5.226978 z m 0,3.484652 c -4.05962,0 -12.196282,2.038521 -12.196282,6.09814 v 4.355815 h 24.392562 v -4.355815 c 0,-4.059619 -8.13666,-6.09814 -12.19628,-6.09814 z m 13.938606,0 c -0.505274,0 -1.080241,0.03485 -1.690055,0.08712 2.021098,1.463552 3.432381,3.432381 3.432381,6.011023 v 4.355815 h 10.453956 v -4.355815 c 0,-4.059619 -8.136663,-6.09814 -12.196282,-6.09814 z" />
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.127.1"
version = "1.127.2"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.127.1"
version = "1.127.2"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"

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).
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

View File

@@ -47,7 +47,7 @@ use types::provider_info::ProviderInfo;
use types::reactions::JSONRPCReactions;
use types::webxdc::WebxdcMessageInfo;
use self::types::message::MessageLoadResult;
use self::types::message::{MessageInfo, MessageLoadResult};
use self::types::{
chat::{BasicChat, JSONRPCChatVisibility, MuteDuration},
location::JsonrpcLocation,
@@ -1126,6 +1126,16 @@ impl CommandApi {
MsgId::new(message_id).get_info(&ctx).await
}
/// Returns additional information for single message.
async fn get_message_info_object(
&self,
account_id: u32,
message_id: u32,
) -> Result<MessageInfo> {
let ctx = self.get_context(account_id).await?;
MessageInfo::from_msg_id(&ctx, MsgId::new(message_id)).await
}
/// Returns contacts that sent read receipts and the time of reading.
async fn get_message_read_receipts(
&self,
@@ -2033,13 +2043,19 @@ impl CommandApi {
text: Option<String>,
file: Option<String>,
quoted_message_id: Option<u32>,
view_type: Option<MessageViewtype>,
) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let mut draft = Message::new(if file.is_some() {
Viewtype::File
} else {
Viewtype::Text
});
let mut draft = Message::new(view_type.map_or_else(
|| {
if file.is_some() {
Viewtype::File
} else {
Viewtype::Text
}
},
|v| v.into(),
));
draft.set_text(text.unwrap_or_default());
if let Some(file) = file {
draft.set_file(file, None);

View File

@@ -552,3 +552,71 @@ pub struct MessageReadReceipt {
pub contact_id: u32,
pub timestamp: i64,
}
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MessageInfo {
rawtext: String,
ephemeral_timer: EphemeralTimer,
/// When message is ephemeral this contains the timestamp of the message expiry
ephemeral_timestamp: Option<i64>,
error: Option<String>,
rfc724_mid: String,
server_urls: Vec<String>,
hop_info: Option<String>,
}
impl MessageInfo {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
let message = Message::load_from_db(context, msg_id).await?;
let rawtext = msg_id.rawtext(context).await?;
let ephemeral_timer = message.get_ephemeral_timer().into();
let ephemeral_timestamp = match message.get_ephemeral_timer() {
deltachat::ephemeral::Timer::Disabled => None,
deltachat::ephemeral::Timer::Enabled { .. } => Some(message.get_ephemeral_timestamp()),
};
let server_urls =
MsgId::get_info_server_urls(context, message.rfc724_mid().to_owned()).await?;
let hop_info = msg_id.hop_info(context).await?;
Ok(Self {
rawtext,
ephemeral_timer,
ephemeral_timestamp,
error: message.error(),
rfc724_mid: message.rfc724_mid().to_owned(),
server_urls,
hop_info,
})
}
}
#[derive(
Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema,
)]
#[serde(rename_all = "camelCase", tag = "variant")]
pub enum EphemeralTimer {
/// Timer is disabled.
Disabled,
/// Timer is enabled.
Enabled {
/// Timer duration in seconds.
///
/// The value cannot be 0.
duration: u32,
},
}
impl From<deltachat::ephemeral::Timer> for EphemeralTimer {
fn from(value: deltachat::ephemeral::Timer) -> Self {
match value {
deltachat::ephemeral::Timer::Disabled => EphemeralTimer::Disabled,
deltachat::ephemeral::Timer::Enabled { duration } => {
EphemeralTimer::Enabled { duration }
}
}
}
}

View File

@@ -55,5 +55,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.127.1"
"version": "1.127.2"
}

View File

@@ -13,16 +13,16 @@ describe("online tests", function () {
before(async function () {
this.timeout(60000);
if (!process.env.DCC_NEW_TMP_EMAIL) {
if (!process.env.CHATMAIL_DOMAIN) {
if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) {
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"
);
process.exit(1);
}
console.log(
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests"
"Missing CHATMAIL_DOMAIN environment variable!, skip integration tests"
);
this.skip();
}
@@ -33,7 +33,7 @@ describe("online tests", function () {
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) {
console.log(
"We didn't got back an account from the api, skip integration tests"
@@ -41,7 +41,7 @@ describe("online tests", function () {
this.skip();
}
account2 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
account2 = createTempUser(process.env.CHATMAIL_DOMAIN);
if (!account2 || !account2.email || !account2.password) {
console.log(
"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) {
const response = await fetch(url, {
method: "POST",
headers: {
"cache-control": "no-cache",
},
});
if (!response.ok) throw new Error("Received invalid response");
return response.json();
export function createTempUser(chatmailDomain: String) {
const charset = "2345789acdefghjkmnpqrstuvwxyz";
let user = "ci-";
for (let i = 0; i < 6; i++) {
user += charset[Math.floor(Math.random() * charset.length)];
}
const email = user + "@" + chatmailDomain;
return { email: email, password: user + "$" + user };
}
function getTargetDir(): Promise<string> {

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.127.1"
version = "1.127.2"
license = "MPL-2.0"
edition = "2021"

View File

@@ -71,6 +71,3 @@ line-length = 120
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
log_cli = true

View File

@@ -160,11 +160,12 @@ class Chat:
text: Optional[str] = None,
file: Optional[str] = None,
quoted_msg: Optional[int] = None,
viewtype: Optional[str] = None,
) -> None:
"""Set draft message."""
if isinstance(quoted_msg, Message):
quoted_msg = quoted_msg.id
self._rpc.misc_set_draft(self.account.id, self.id, text, file, quoted_msg)
self._rpc.misc_set_draft(self.account.id, self.id, text, file, quoted_msg, viewtype)
def remove_draft(self) -> None:
"""Remove draft message."""

View File

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

View File

@@ -377,3 +377,15 @@ def test_provider_info(rpc) -> None:
rpc.set_config(account_id, "socks5_enabled", "1")
provider_info = rpc.get_provider_info(account_id, "github.com")
assert provider_info is None
def test_qr_setup_contact(acfactory) -> None:
alice, bob = acfactory.get_online_accounts(2)
qr_code, _svg = alice.get_qr_code()
bob.secure_join(qr_code)
while True:
event = alice.wait_for_event()
if event["kind"] == "SecurejoinInviterProgress" and event["progress"] == 1000:
return

View File

@@ -11,7 +11,7 @@ setenv =
# Avoid stack overflow when Rust core is built without optimizations.
RUST_MIN_STACK=8388608
passenv =
DCC_NEW_TMP_EMAIL
CHATMAIL_DOMAIN
deps =
pytest
pytest-timeout
@@ -28,4 +28,6 @@ commands =
ruff src/ examples/ tests/
[pytest]
timeout = 60
timeout = 300
log_cli = true
log_level = info

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.127.1"
version = "1.127.2"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -57,7 +57,7 @@ Note that usually a mail is signed by a key that has a UID matching the from add
### Notes:
- We treat protected and non-protected chats the same
- We leave the aeap transition statement away since it seems not to be needed, makes things harder on the sending side, wastes some network traffic, and is worse for privacy (since more pepole know what old addresses you had).
- We leave the aeap transition statement away since it seems not to be needed, makes things harder on the sending side, wastes some network traffic, and is worse for privacy (since more people know what old addresses you had).
- As soon as we encrypt read receipts, sending a read receipt will be enough to tell a lot of people that you transitioned
- AEAP will make the problem of inconsistent group state worse, both because it doesn't work if the message is unencrypted (even if the design allowed it, it would be problematic security-wise) and because some chat partners may have gotten the transition and some not. We should do something against this at some point in the future, like asking the user whether they want to add/remove the members to restore consistent group state.

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.
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
```

View File

@@ -12,22 +12,14 @@ import fetch from 'node-fetch'
chai.use(chaiAsPromised)
chai.config.truncateThreshold = 0 // Do not truncate assertion errors.
async function createTempUser(url) {
async function postData(url = '') {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
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
function createTempUser(chatmailDomain) {
const charset = "2345789acdefghjkmnpqrstuvwxyz";
let user = "ci-";
for (let i = 0; i < 6; i++) {
user += charset[Math.floor(Math.random() * charset.length)];
}
return await postData(url)
const email = user + "@" + chatmailDomain;
return { email: email, password: user + "$" + user };
}
describe('static tests', function () {
@@ -448,12 +440,6 @@ describe('Offline Tests with unconfigured account', function () {
expect(blobPath.startsWith(blobs)).to.be.true
expect(blobPath.includes('image')).to.be.true
expect(blobPath.endsWith('.jpeg')).to.be.true
context.setChatProfileImage(chatId, null)
expect(context.getChat(chatId).getProfileImage()).to.be.equal(
null,
'image is null'
)
})
it('test setting ephemeral timer', function () {
@@ -768,14 +754,7 @@ describe('Integration tests', function () {
})
this.beforeAll(async function () {
if (!process.env.DCC_NEW_TMP_EMAIL) {
console.log(
'Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests'
)
this.skip()
}
account = await createTempUser(process.env.DCC_NEW_TMP_EMAIL)
account = createTempUser(process.env.CHATMAIL_DOMAIN)
if (!account || !account.email || !account.password) {
console.log(
"We didn't got back an account from the api, skip integration tests"

View File

@@ -60,5 +60,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.127.1"
"version": "1.127.2"
}

View File

@@ -53,14 +53,14 @@ end-to-end tests that require accounts on real e-mail servers.
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 http://testrun.org server.
These accounts are removed automatically as they expire.
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}`.
After setting the variable, either rerun `scripts/run-python-test.sh`
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::
$ docker run -e DCC_NEW_TMP_EMAIL \
$ docker run -e CHATMAIL_DOMAIN \
--rm -it -v $(pwd):/mnt -w /mnt \
deltachat/coredeps scripts/run_all.sh

View File

@@ -8,11 +8,11 @@ import sys
import threading
import time
import weakref
import random
from queue import Queue
from typing import Callable, Dict, List, Optional, Set
import pytest
import requests
from _pytest._code import Source
import deltachat
@@ -29,6 +29,12 @@ def pytest_addoption(parser):
default=None,
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(
"--ignored",
action="store_true",
@@ -53,10 +59,12 @@ def pytest_addoption(parser):
def pytest_configure(config):
cfg = config.getoption("--liveconfig")
cfg = config.getoption("--chatmail")
if not cfg:
cfg = os.getenv("DCC_NEW_TMP_EMAIL")
cfg = os.getenv("CHATMAIL_DOMAIN")
if cfg:
config.option.liveconfig = cfg
config.option.chatmail = cfg
# 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
@@ -157,13 +165,29 @@ class TestProcess:
"""provide live account configs, cached on a per-test-process scope
so that test functions can re-use already known live configs.
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")
if not liveconfig_opt:
pytest.skip("specify DCC_NEW_TMP_EMAIL or --liveconfig to provide live accounts")
if not liveconfig_opt.startswith("http"):
chatmail_opt = self.pytestconfig.getoption("--chatmail")
if chatmail_opt:
# Use a chatmail instance.
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):
if line.strip() and not line.strip().startswith("#"):
d = {}
@@ -174,20 +198,9 @@ class TestProcess:
yield from iter(self._configlist)
else:
MAX_LIVE_CREATED_ACCOUNTS = 10
for index in range(MAX_LIVE_CREATED_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.")
pytest.skip(
"specify --liveconfig, CHATMAIL_DOMAIN or --chatmail to provide live accounts",
)
def cache_maybe_retrieve_configured_db_files(self, cache_addr, 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):
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")
assert ac2.get_config("configured_trash_folder")
chat12 = acfactory.get_accepted_chat(ac1, ac2)
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):
configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account()
ac1.update_config(configdict)
ac1.set_config("mail_pw", "abc") # Wrong mail pw
ac1.configure()
(ac1,) = acfactory.get_online_accounts(1)
ac2 = acfactory.get_unconfigured_account()
ac2.set_config("addr", ac1.get_config("addr"))
ac2.set_config("mail_pw", "abc") # Wrong mail pw
ac2.configure()
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:
break
# Password is wrong so it definitely has to say something about "password"
@@ -2548,7 +2560,7 @@ class TestOnlineConfigureFails:
def test_invalid_user(self, acfactory):
configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account()
configdict["addr"] = "x" + configdict["addr"]
configdict["addr"] = "$" + configdict["addr"]
ac1.update_config(configdict)
configtracker = ac1.configure()
configtracker.wait_progress(500)
@@ -2557,7 +2569,7 @@ class TestOnlineConfigureFails:
def test_invalid_domain(self, acfactory):
configdict = acfactory.get_next_liveconfig()
ac1 = acfactory.get_unconfigured_account()
configdict["addr"] += "x"
configdict["addr"] += "$"
ac1.update_config(configdict)
configtracker = ac1.configure()
configtracker.wait_progress(500)

View File

@@ -16,7 +16,7 @@ setenv =
passenv =
DCC_RS_DEV
DCC_RS_TARGET
DCC_NEW_TMP_EMAIL
CHATMAIL_DOMAIN
CARGO_TARGET_DIR
RUSTC_WRAPPER
deps =
@@ -85,7 +85,7 @@ commands =
addopts = -v -ra --strict-markers
norecursedirs = .tox
xfail_strict=true
timeout = 150
timeout = 300
timeout_func_only = True
markers =
ignored: ignore this test in default test runs, use --ignored to run.

View File

@@ -1 +1 @@
2023-10-27
2023-10-29

View File

@@ -3,4 +3,4 @@ set -euo pipefail
tox -c deltachat-rpc-client -e py --devenv venv
venv/bin/pip install --upgrade pip
cargo install --path deltachat-rpc-server/ --root "$PWD/venv"
cargo install --path deltachat-rpc-server/ --root "$PWD/venv" --debug

View File

@@ -28,7 +28,7 @@ ssh $SSHTARGET <<_HERE
export RUSTC_WRAPPER=\`which sccache\`
cd $BUILDDIR
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
#rm -rf virtualenv venv

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
cargo install --path deltachat-rpc-server/ --root "$PWD/venv"
cargo install --path deltachat-rpc-server/ --root "$PWD/venv" --debug
PATH="$PWD/venv/bin:$PATH" tox -c deltachat-rpc-client

View File

@@ -27,7 +27,7 @@ mkdir -p $TOXWORKDIR
# XXX we may switch on some live-tests on for better ensurances
# Note that the independent remote_tests_python step does all kinds of
# 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.
# E.g. musllinux_1_1 does not have PyPy interpreters as of 2022-07-10

View File

@@ -192,7 +192,7 @@ in different orders, esp. on creating new groups.
To remove a member, a `Chat-Group-Member-Removed` header must be sent
with the value set to the email-address of the member to remove.
When receiving a `Chat-Group-Member-Removed` header,
only exaxtly the given member has to be removed from the member list.
only exactly the given member has to be removed from the member list.
Messenger clients MUST NOT construct the member list
on other group messages
@@ -339,7 +339,7 @@ only on image changes.
In older specs, the profile-image was sent as an attachment
and `Chat-User-Avatar:` specified its name.
However, it turned out that these attachments are kind of unuexpected to users,
However, it turned out that these attachments are kind of unexpected to users,
therefore the profile-image go to the header now.

View File

@@ -1451,6 +1451,12 @@ impl Chat {
if let Ok(image_rel) = get_archive_icon(context).await {
return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
}
}
if self.typ == Chattype::Group {
if let Ok(image_rel) = get_group_icon(context).await {
return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
}
} else if self.typ == Chattype::Single {
let contacts = get_chat_contacts(context, self.id).await?;
if let Some(contact_id) = contacts.first() {
@@ -1983,6 +1989,21 @@ pub(crate) async fn get_broadcast_icon(context: &Context) -> Result<String> {
Ok(icon)
}
pub(crate) async fn get_group_icon(context: &Context) -> Result<String> {
if let Some(icon) = context.sql.get_raw_config("icon-group").await? {
return Ok(icon);
}
let icon = include_bytes!("../assets/icon-group.png");
let blob = BlobObject::create(context, "icon-group.png", icon).await?;
let icon = blob.as_name().to_string();
context
.sql
.set_raw_config("icon-group", Some(&icon))
.await?;
Ok(icon)
}
pub(crate) async fn get_archive_icon(context: &Context) -> Result<String> {
if let Some(icon) = context.sql.get_raw_config("icon-archive").await? {
return Ok(icon);
@@ -4347,8 +4368,16 @@ mod tests {
assert_eq!(a1_chat.grpid, a2_chat.grpid);
assert_eq!(a1_chat.name, "foo");
assert_eq!(a2_chat.name, "foo");
assert_eq!(a1_chat.get_profile_image(&a1).await?, None);
assert_eq!(a2_chat.get_profile_image(&a2).await?, None);
assert!(a1_chat
.get_profile_image(&a1)
.await?
.unwrap()
.ends_with("icon-group.png"));
assert!(a2_chat
.get_profile_image(&a2)
.await?
.unwrap()
.ends_with("icon-group.png"));
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 1);
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 1);

View File

@@ -150,21 +150,53 @@ WHERE id=?;
self.0
}
/// Returns raw text of a message, used for message info
pub async fn rawtext(self, context: &Context) -> Result<String> {
Ok(context
.sql
.query_get_value("SELECT txt_raw FROM msgs WHERE id=?", (self,))
.await?
.unwrap_or_default())
}
/// Returns server foldernames and UIDs of a message, used for message info
pub async fn get_info_server_urls(
context: &Context,
rfc724_mid: String,
) -> Result<Vec<String>> {
context
.sql
.query_map(
"SELECT folder, uid FROM imap WHERE rfc724_mid=?",
(rfc724_mid,),
|row| {
let folder: String = row.get("folder")?;
let uid: u32 = row.get("uid")?;
Ok(format!("</{folder}/;UID={uid}>"))
},
|rows| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await
}
/// Returns information about hops of a message, used for message info
pub async fn hop_info(self, context: &Context) -> Result<Option<String>> {
context
.sql
.query_get_value("SELECT hop_info FROM msgs WHERE id=?", (self,))
.await
}
/// Returns detailed message information in a multi-line text form.
pub async fn get_info(self, context: &Context) -> Result<String> {
let msg = Message::load_from_db(context, self).await?;
let rawtxt: Option<String> = context
.sql
.query_get_value("SELECT txt_raw FROM msgs WHERE id=?", (self,))
.await?;
let rawtxt: String = self.rawtext(context).await?;
let mut ret = String::new();
if rawtxt.is_none() {
ret += &format!("Cannot load message {self}.");
return Ok(ret);
}
let rawtxt = rawtxt.unwrap_or_default();
let rawtxt = truncate(rawtxt.trim(), DC_DESIRED_TEXT_LEN);
let fts = timestamp_to_str(msg.get_timestamp());
@@ -282,32 +314,13 @@ WHERE id=?;
if !msg.rfc724_mid.is_empty() {
ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
let server_uids = context
.sql
.query_map(
"SELECT folder, uid FROM imap WHERE rfc724_mid=?",
(msg.rfc724_mid,),
|row| {
let folder: String = row.get("folder")?;
let uid: u32 = row.get("uid")?;
Ok((folder, uid))
},
|rows| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
for (folder, uid) in server_uids {
let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
for server_url in server_urls {
// Format as RFC 5092 relative IMAP URL.
ret += &format!("\n</{folder}/;UID={uid}>");
ret += &format!("\n{server_url}");
}
}
let hop_info: Option<String> = context
.sql
.query_get_value("SELECT hop_info FROM msgs WHERE id=?;", (self,))
.await?;
let hop_info = self.hop_info(context).await?;
ret += "\n\n";
ret += &hop_info.unwrap_or_else(|| "No Hop Info".to_owned());
@@ -649,6 +662,12 @@ impl Message {
self.id
}
/// Returns the rfc724 message ID
/// May be empty
pub fn rfc724_mid(&self) -> &str {
&self.rfc724_mid
}
/// Returns the ID of the contact who wrote the message.
pub fn get_from_id(&self) -> ContactId {
self.from_id

View File

@@ -1358,7 +1358,7 @@ impl MimeMessage {
self.get_mailinglist_header().is_some()
}
pub fn repl_msg_by_error(&mut self, error_msg: &str) {
pub fn replace_msg_by_error(&mut self, error_msg: &str) {
self.is_system_message = SystemMessage::Unknown;
if let Some(part) = self.parts.first_mut() {
part.typ = Viewtype::Text;

View File

@@ -637,7 +637,7 @@ async fn add_parts(
chat_id = None;
} else {
let s = stock_str::unknown_sender_for_chat(context).await;
mime_parser.repl_msg_by_error(&s);
mime_parser.replace_msg_by_error(&s);
}
} else {
// In non-protected chats, just mark the sender as overridden. Therefore, the UI will prepend `~`
@@ -1055,7 +1055,7 @@ async fn add_parts(
{
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
mime_parser.replace_msg_by_error(&s);
}
}
}
@@ -1596,7 +1596,7 @@ async fn create_or_lookup_group(
{
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
mime_parser.replace_msg_by_error(&s);
}
ProtectionStatus::Protected
} else {
@@ -1758,7 +1758,7 @@ async fn apply_group_changes(
{
warn!(context, "Verification problem: {err:#}.");
let s = format!("{err}. See 'Info' for more details");
mime_parser.repl_msg_by_error(&s);
mime_parser.replace_msg_by_error(&s);
}
if !chat.is_protected() {
@@ -2311,7 +2311,7 @@ async fn has_verified_encryption(
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
sql::repeat_vars(to_ids.len())
),
rusqlite::params_from_iter(to_ids),
rusqlite::params_from_iter(&to_ids),
|row| {
let to_addr: String = row.get(0)?;
let is_verified: i32 = row.get(1).unwrap_or(0);
@@ -2364,8 +2364,7 @@ async fn has_verified_encryption(
}
if !is_verified {
return Ok(NotVerified(format!(
"{} is not a member of this protected chat",
to_addr
"{to_addr} is not a member of this protected chat member list {to_ids:?}",
)));
}
}

View File

@@ -38,11 +38,11 @@ While this describes an already working "proof of concept" usage there are alrea
webxdc empowers FOSS developments in unprecedented ways:
- well-known paradim: use all the existing JS/html5 libraries and designs of your choice
- well-known paradigm: use all the existing JS/html5 libraries and designs of your choice
- quick onboarding: only a handful API methods to learn
- serverless (but really): no worrying about hosting a server or configuring DNS, AWS etc
- permissionless: no worrying about registering at app stores for distribution
- unbuerocratic: no worrying about login/password/expiry procedures or leaks
- unbureaucratic: no worrying about login/password/expiry procedures or leaks
- secure: no worrying about cryptographic algos or e2e-encryption complexity
On the flip side, you need to learn how to do state updates between instances of your webxdc apps. This is a classic P2P problem and there are simple (send full state update) and advanced ways (use CRDTs or similar) to arrange decentralized state. In any case, there is no DHT let alone blockchain needed and thus no Crypto or coin needed, either.