Browse Source

Merge remote-tracking branch 'upstream/main' into bump_to_v0.9.0

bump_to_v0.9.0
Sasha Wasilewska 8 months ago
parent
commit
f82b689087
Signed by: commagray GPG Key ID: 28DC882DA80E623A
  1. 189
      .drone.yml
  2. 3
      .gitignore
  3. 4
      .gitmodules
  4. 1112
      Cargo.lock
  5. 88
      Cargo.toml
  6. 62
      RELEASES.md
  7. 2
      ansible/VERSION
  8. 8
      ansible/templates/nginx.conf
  9. 51
      api_tests/.eslintrc.json
  10. 4
      api_tests/.prettierrc.js
  11. 15
      api_tests/package.json
  12. 90
      api_tests/prepare-drone-federation-test.sh
  13. 20
      api_tests/run-federation-test.sh
  14. 347
      api_tests/src/comment.spec.ts
  15. 142
      api_tests/src/community.spec.ts
  16. 23
      api_tests/src/follow.spec.ts
  17. 364
      api_tests/src/post.spec.ts
  18. 64
      api_tests/src/private_message.spec.ts
  19. 219
      api_tests/src/shared.ts
  20. 61
      api_tests/src/user.spec.ts
  21. 16
      api_tests/tsconfig.json
  22. 2621
      api_tests/yarn.lock
  23. 7
      clean.sh
  24. 51
      crates/api/Cargo.toml
  25. 2
      crates/api/src/claims.rs
  26. 251
      crates/api/src/comment.rs
  27. 221
      crates/api/src/community.rs
  28. 63
      crates/api/src/lib.rs
  29. 186
      crates/api/src/post.rs
  30. 180
      crates/api/src/site.rs
  31. 271
      crates/api/src/user.rs
  32. 1
      crates/api/src/version.rs
  33. 97
      crates/api/src/websocket.rs
  34. 52
      crates/apub/Cargo.toml
  35. 0
      crates/apub/src/activities/mod.rs
  36. 112
      crates/apub/src/activities/receive/comment.rs
  37. 54
      crates/apub/src/activities/receive/comment_undo.rs
  38. 24
      crates/apub/src/activities/receive/community.rs
  39. 4
      crates/apub/src/activities/receive/mod.rs
  40. 79
      crates/apub/src/activities/receive/post.rs
  41. 54
      crates/apub/src/activities/receive/post_undo.rs
  42. 67
      crates/apub/src/activities/receive/private_message.rs
  43. 65
      crates/apub/src/activities/send/comment.rs
  44. 10
      crates/apub/src/activities/send/community.rs
  45. 0
      crates/apub/src/activities/send/mod.rs
  46. 17
      crates/apub/src/activities/send/post.rs
  47. 5
      crates/apub/src/activities/send/private_message.rs
  48. 9
      crates/apub/src/activities/send/user.rs
  49. 75
      crates/apub/src/activity_queue.rs
  50. 0
      crates/apub/src/extensions/context.rs
  51. 3
      crates/apub/src/extensions/group_extensions.rs
  52. 0
      crates/apub/src/extensions/mod.rs
  53. 0
      crates/apub/src/extensions/page_extension.rs
  54. 5
      crates/apub/src/extensions/signatures.rs
  55. 147
      crates/apub/src/fetcher/community.rs
  56. 82
      crates/apub/src/fetcher/fetch.rs
  57. 72
      crates/apub/src/fetcher/mod.rs
  58. 83
      crates/apub/src/fetcher/objects.rs
  59. 206
      crates/apub/src/fetcher/search.rs
  60. 71
      crates/apub/src/fetcher/user.rs
  61. 5
      crates/apub/src/http/comment.rs
  62. 22
      crates/apub/src/http/community.rs
  63. 5
      crates/apub/src/http/mod.rs
  64. 5
      crates/apub/src/http/post.rs
  65. 37
      crates/apub/src/http/user.rs
  66. 17
      crates/apub/src/inbox/community_inbox.rs
  67. 13
      crates/apub/src/inbox/mod.rs
  68. 116
      crates/apub/src/inbox/receive_for_community.rs
  69. 10
      crates/apub/src/inbox/shared_inbox.rs
  70. 14
      crates/apub/src/inbox/user_inbox.rs
  71. 132
      crates/apub/src/lib.rs
  72. 56
      crates/apub/src/objects/comment.rs
  73. 39
      crates/apub/src/objects/community.rs
  74. 227
      crates/apub/src/objects/mod.rs
  75. 42
      crates/apub/src/objects/post.rs
  76. 29
      crates/apub/src/objects/private_message.rs
  77. 48
      crates/apub/src/objects/user.rs
  78. 25
      crates/db_queries/Cargo.toml
  79. 230
      crates/db_queries/src/aggregates/comment_aggregates.rs
  80. 269
      crates/db_queries/src/aggregates/community_aggregates.rs
  81. 5
      crates/db_queries/src/aggregates/mod.rs
  82. 239
      crates/db_queries/src/aggregates/post_aggregates.rs
  83. 186
      crates/db_queries/src/aggregates/site_aggregates.rs
  84. 250
      crates/db_queries/src/aggregates/user_aggregates.rs
  85. 97
      crates/db_queries/src/lib.rs
  86. 66
      crates/db_queries/src/source/activity.rs
  87. 31
      crates/db_queries/src/source/category.rs
  88. 261
      crates/db_queries/src/source/comment.rs
  89. 51
      crates/db_queries/src/source/comment_report.rs
  90. 325
      crates/db_queries/src/source/community.rs
  91. 13
      crates/db_queries/src/source/mod.rs
  92. 264
      crates/db_queries/src/source/moderator.rs
  93. 64
      crates/db_queries/src/source/password_reset_request.rs
  94. 287
      crates/db_queries/src/source/post.rs
  95. 48
      crates/db_queries/src/source/post_report.rs
  96. 148
      crates/db_queries/src/source/private_message.rs
  97. 45
      crates/db_queries/src/source/site.rs
  98. 449
      crates/db_queries/src/source/user.rs
  99. 69
      crates/db_queries/src/source/user_mention.rs
  100. 12
      crates/db_schema/Cargo.toml

189
.drone.yml

@ -0,0 +1,189 @@
---
kind: pipeline
name: amd64
platform:
os: linux
arch: amd64
steps:
- name: fetch git submodules
image: node:15-alpine3.12
commands:
- apk add git
- git submodule update --init --recursive --remote
- name: chown repo
image: ekidd/rust-musl-builder:1.47.0
user: root
commands:
- chown 1000:1000 . -R
- name: check formatting
image: rustdocker/rust:nightly
commands:
- /root/.cargo/bin/cargo fmt -- --check
- name: cargo clippy
image: ekidd/rust-musl-builder:1.47.0
commands:
- cargo clippy --workspace --tests --all-targets --all-features -- -D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro
- name: cargo test
image: ekidd/rust-musl-builder:1.47.0
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: 1
RUST_TEST_THREADS: 1
commands:
- sudo apt-get update
- sudo apt-get -y install --no-install-recommends espeak postgresql-client
- cargo test --workspace --no-fail-fast
- name: cargo build
image: ekidd/rust-musl-builder:1.47.0
commands:
- cargo build
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
- name: run federation tests
image: node:15-alpine3.12
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: 1
commands:
- apk add bash curl postgresql-client
- bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/
- yarn
- yarn api-test
- name: make release build and push to docker hub
image: plugins/docker
settings:
dockerfile: docker/prod/Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: dessalines/lemmy
auto_tag: true
auto_tag_suffix: linux-amd64
when:
ref:
- refs/tags/*
- name: push to docker manifest
image: plugins/manifest
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
target: "dessalines/lemmy:${DRONE_TAG}"
template: "dessalines/lemmy:${DRONE_TAG}-OS-ARCH"
platforms:
- linux/amd64
- linux/arm64
ignore_missing: true
when:
ref:
- refs/tags/*
services:
- name: database
image: postgres:12-alpine
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password
---
kind: pipeline
name: arm64
platform:
os: linux
arch: arm64
steps:
- name: fetch git submodules
image: node:15-alpine3.12
commands:
- apk add git
- git submodule update --init --recursive --remote
- name: cargo test
image: rust:1.47-slim-buster
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: 1
RUST_TEST_THREADS: 1
commands:
- apt-get update
- apt-get -y install --no-install-recommends espeak postgresql-client libssl-dev pkg-config libpq-dev
- cargo test --workspace --no-fail-fast
- cargo build
# Using Debian here because there seems to be no official Alpine-based Rust docker image for ARM.
- name: cargo build
image: rust:1.47-slim-buster
commands:
- apt-get update
- apt-get -y install --no-install-recommends libssl-dev pkg-config libpq-dev
- cargo build
- mv target/debug/lemmy_server target/lemmy_server
- name: run federation tests
image: node:15-buster-slim
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: 1
commands:
- mkdir -p /usr/share/man/man1 /usr/share/man/man7
- apt-get update
- apt-get -y install --no-install-recommends bash curl libssl-dev pkg-config libpq-dev postgresql-client libc6-dev
- bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/
- yarn
- yarn api-test
- name: make release build and push to docker hub
image: plugins/docker
settings:
dockerfile: docker/prod/Dockerfile.arm
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: dessalines/lemmy
auto_tag: true
auto_tag_suffix: linux-arm64
when:
ref:
- refs/tags/*
- name: push to docker manifest
image: plugins/manifest
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
target: "dessalines/lemmy:${DRONE_TAG}"
template: "dessalines/lemmy:${DRONE_TAG}-OS-ARCH"
platforms:
- linux/amd64
- linux/arm64
ignore_missing: true
when:
ref:
- refs/tags/*
services:
- name: database
image: postgres:12-alpine
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password

3
.gitignore

@ -15,8 +15,7 @@ volumes
# local build files
target
env_setup.sh
query_testing/*.json
query_testing/*.json.old
query_testing/**/reports/*.json
# API tests
api_tests/node_modules

4
.gitmodules

@ -0,0 +1,4 @@
[submodule "docs"]
path = docs
url = https://github.com/LemmyNet/lemmy-docs
branch = main

1112
Cargo.lock

File diff suppressed because it is too large

88
Cargo.toml

@ -3,54 +3,60 @@ name = "lemmy_server"
version = "0.0.1"
edition = "2018"
[profile.release]
lto = true
[profile.dev]
debug = 0
[workspace]
members = [
"lemmy_api",
"lemmy_apub",
"lemmy_utils",
"lemmy_db",
"lemmy_structs",
"lemmy_rate_limit",
"lemmy_websocket",
"crates/api",
"crates/apub",
"crates/utils",
"crates/db_queries",
"crates/db_schema",
"crates/db_views",
"crates/db_views_actor",
"crates/db_views_actor",
"crates/structs",
"crates/websocket",
]
[dependencies]
lemmy_api = { path = "./lemmy_api" }
lemmy_apub = { path = "./lemmy_apub" }
lemmy_utils = { path = "./lemmy_utils" }
lemmy_db = { path = "./lemmy_db" }
lemmy_structs = { path = "./lemmy_structs" }
lemmy_rate_limit = { path = "./lemmy_rate_limit" }
lemmy_websocket = { path = "./lemmy_websocket" }
diesel = "1.4"
diesel_migrations = "1.4"
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
actix = "0.10"
actix-web = { version = "3.1", default-features = false, features = ["rustls"] }
actix-files = { version = "0.4", default-features = false }
actix-web-actors = { version = "3.0", default-features = false }
awc = { version = "2.0", default-features = false }
log = "0.4"
env_logger = "0.8"
strum = "0.19"
lazy_static = "1.3"
rss = "1.9"
url = { version = "2.1", features = ["serde"] }
openssl = "0.10"
http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] }
tokio = "0.3"
sha2 = "0.9"
anyhow = "1.0"
reqwest = { version = "0.10", features = ["json"] }
activitystreams = "0.7.0-alpha.4"
actix-rt = { version = "1.1", default-features = false }
serde_json = { version = "1.0", features = ["preserve_order"]}
lemmy_api = { path = "./crates/api" }
lemmy_apub = { path = "./crates/apub" }
lemmy_utils = { path = "./crates/utils" }
lemmy_db_schema = { path = "./crates/db_schema" }
lemmy_db_queries = { path = "./crates/db_queries" }
lemmy_db_views = { path = "./crates/db_views" }
lemmy_db_views_moderator = { path = "./crates/db_views_moderator" }
lemmy_db_views_actor = { path = "./crates/db_views_actor" }
lemmy_structs = { path = "./crates/structs" }
lemmy_websocket = { path = "./crates/websocket" }
diesel = "1.4.5"
diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-files = { version = "0.4.1", default-features = false }
actix-web-actors = { version = "3.0.0", default-features = false }
awc = { version = "2.0.3", default-features = false }
log = "0.4.11"
env_logger = "0.8.2"
strum = "0.20.0"
lazy_static = "1.4.0"
rss = "1.9.0"
url = { version = "2.2.0", features = ["serde"] }
openssl = "0.10.31"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
tokio = "0.3.6"
sha2 = "0.9.2"
anyhow = "1.0.36"
reqwest = { version = "0.10.10", features = ["json"] }
activitystreams = "0.7.0-alpha.8"
actix-rt = { version = "1.1.1", default-features = false }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
[dev-dependencies.cargo-husky]
version = "1"
version = "1.5.0"
default-features = false # Disable features which are enabled by default
features = ["precommit-hook", "run-cargo-fmt", "run-cargo-clippy"]

62
RELEASES.md

@ -1,3 +1,65 @@
# Lemmy v0.9.0 Release (2021-01-25)
## Changes
Since our last release in October of last year, and we've had [~450](https://github.com/LemmyNet/lemmy/compare/v0.8.0...main) commits.
The biggest changes, as we'll outline below, are a re-work of Lemmy's database structure, a `v2` of Lemmy's API, and activitypub compliance fixes. The new re-worked DB is much faster, easier to maintain, and [now supports hierarchical rather than flat objects in the new API](https://github.com/LemmyNet/lemmy/issues/1275).
We've also seen the first release of [Lemmur](https://github.com/krawieck/lemmur/releases/tag/v0.1.1), an android / iOS (soon) / windows / linux client, as well as [Lemmer](https://github.com/uuttff8/Lemmy-iOS), a native iOS client. Much thanks to @krawieck, @shilangyu, and @uuttff8 for making these great clients. If you can, please contribute to their [patreon](https://www.patreon.com/lemmur) to help fund lemmur development.
## LemmyNet projects
### Lemmy Server
- [Moved views from SQL to Diesel](https://github.com/LemmyNet/lemmy/issues/1275). This was a spinal replacement for much of lemmy.
- Removed all the old fast_tables and triggers, and created new aggregates tables.
- Added a `v2` of the API to support the hierarchical objects created from the above changes.
- Moved continuous integration to [drone](https://cloud.drone.io/LemmyNet/lemmy/), now includes formatting, clippy, and cargo build checks, unit testing, and federation testing. [Drone also deploys both amd64 and arm64 images to dockerhub.](https://hub.docker.com/r/dessalines/lemmy)
- Split out documentation into git submodule.
- Shortened slur filter to avoid false positives.
- Added query performance testing and comparisons. Added indexes to make sure every query is `< 30 ms`.
- Added compilation time testing.
### Lemmy javascript / typescript client
- Updated the [lemmy-js-client](https://github.com/LemmyNet/lemmy-js-client) to use the new `v2` API. Our API docs now reference this project's files, to show what the http / websocket forms and responses should look like.
- Drone now handles publishing its [npm packages.](https://www.npmjs.com/package/lemmy-js-client)
### Lemmy-UI
- Updated it to use the `v2` API via `lemmy-js-client`, required changing nearly every component.
- Added a live comment count.
- Added drone deploying, and builds for ARM.
- Fixed community link wrapping.
- Various other bug fixes.
### Lemmy Docs
- We moved documentation into a separate git repository, and support translation for the docs now!
- Moved our code of conduct into the documentation.
### Federation
This release includes some bug fixes for federation, and some changes to get us closer to compliance with the ActivityPub standard.
- fixed: [Community bans not federating](https://github.com/LemmyNet/lemmy/issues/1287)
- fixed: [Local posts sometimes got marked as remote](https://github.com/LemmyNet/lemmy/issues/1302)
- fixed: [Creator of post/comment is not notified about new child comments](https://github.com/LemmyNet/lemmy/issues/1325)
- fixed: [Community deletion not federated](https://github.com/LemmyNet/lemmy/issues/1256)
None of these are breaking changes, so federation between 0.9.0 and 0.8.11 will work without problems.
## Upgrading
If you'd like to make a DB backup before upgrading, follow [this guide](https://lemmy.ml/docs/en/administration/backup_and_restore.html).
- [Upgrade with manual Docker installation](https://lemmy.ml/docs/en/administration/install_docker.html#updating)
- [Upgrade with Ansible installation](https://lemmy.ml/docs/en/administration/install_ansible.html)
# Lemmy v0.8.0 Release (2020-10-16)
## Changes

2
ansible/VERSION

@ -1 +1 @@
v0.8.10
0.9.0

8
ansible/templates/nginx.conf

@ -61,12 +61,20 @@ server {
if ($http_accept = "application/activity+json") {
set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
}
if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
}
if ($request_method = POST) {
set $proxpass "http://0.0.0.0:{{ lemmy_port }}";
}
proxy_pass $proxpass;
rewrite ^(.+)/+$ $1 permanent;
# Send actual client IP upstream
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# backend

51
api_tests/.eslintrc.json

@ -0,0 +1,51 @@
{
"root": true,
"env": {
"browser": true
},
"plugins": [
"jane"
],
"extends": [
"plugin:jane/recommended",
"plugin:jane/typescript"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"warnOnUnsupportedTypeScriptVersion": false
},
"rules": {
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/no-empty-interface": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-this-alias": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-useless-constructor": 0,
"arrow-body-style": 0,
"curly": 0,
"eol-last": 0,
"eqeqeq": 0,
"func-style": 0,
"import/no-duplicates": 0,
"max-statements": 0,
"max-params": 0,
"new-cap": 0,
"no-console": 0,
"no-duplicate-imports": 0,
"no-extra-parens": 0,
"no-return-assign": 0,
"no-throw-literal": 0,
"no-trailing-spaces": 0,
"no-unused-expressions": 0,
"no-useless-constructor": 0,
"no-useless-escape": 0,
"no-var": 0,
"prefer-const": 0,
"prefer-rest-params": 0,
"quote-props": 0,
"unicorn/filename-case": 0
}
}

4
api_tests/.prettierrc.js

@ -0,0 +1,4 @@
module.exports = Object.assign(require('eslint-plugin-jane/prettier-ts'), {
arrowParens: 'avoid',
semi: true,
});

15
api_tests/package.json

@ -7,14 +7,19 @@
"author": "Dessalines",
"license": "AGPL-3.0",
"scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
"fix": "prettier --write src && eslint --fix src",
"api-test": "jest src/ -i --verbose"
},
"devDependencies": {
"@types/jest": "^26.0.14",
"jest": "^26.4.2",
"lemmy-js-client": "^1.0.14",
"@types/jest": "^26.0.20",
"eslint": "^7.18.0",
"eslint-plugin-jane": "^9.0.3",
"jest": "^26.6.3",
"lemmy-js-client": "0.9.0-rc.12",
"node-fetch": "^2.6.1",
"ts-jest": "^26.4.1",
"typescript": "^4.0.3"
"prettier": "^2.1.2",
"ts-jest": "^26.4.4",
"typescript": "^4.1.3"
}
}

90
api_tests/prepare-drone-federation-test.sh

@ -0,0 +1,90 @@
#!/bin/bash
set -e
export LEMMY_JWT_SECRET=changeme
export LEMMY_FEDERATION__ENABLED=true
export LEMMY_TLS_ENABLED=false
export LEMMY_SETUP__ADMIN_PASSWORD=lemmy
export LEMMY_RATE_LIMIT__POST=99999
export LEMMY_RATE_LIMIT__REGISTER=99999
export LEMMY_CAPTCHA__ENABLED=false
export LEMMY_TEST_SEND_SYNC=1
export RUST_BACKTRACE=1
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE"
psql "${LEMMY_DATABASE_URL}/lemmy" -c "CREATE DATABASE $INSTANCE"
done
if [ -z "$DO_WRITE_HOSTS_FILE" ]; then
if ! grep -q lemmy-alpha /etc/hosts; then
echo "Please add the following to your /etc/hosts file, then press enter:
127.0.0.1 lemmy-alpha
127.0.0.1 lemmy-beta
127.0.0.1 lemmy-gamma
127.0.0.1 lemmy-delta
127.0.0.1 lemmy-epsilon"
read -p ""
fi
else
for INSTANCE in lemmy-alpha lemmy-beta lemmy-gamma lemmy-delta lemmy-epsilon; do
echo "127.0.0.1 $INSTANCE" >> /etc/hosts
done
fi
killall lemmy_server || true
echo "start alpha"
LEMMY_HOSTNAME=lemmy-alpha:8541 \
LEMMY_PORT=8541 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_alpha" \
LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha \
LEMMY_SETUP__SITE_NAME=lemmy-alpha \
target/lemmy_server >/dev/null 2>&1 &
echo "start beta"
LEMMY_HOSTNAME=lemmy-beta:8551 \
LEMMY_PORT=8551 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_beta" \
LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta \
LEMMY_SETUP__SITE_NAME=lemmy-beta \
target/lemmy_server >/dev/null 2>&1 &
echo "start gamma"
LEMMY_HOSTNAME=lemmy-gamma:8561 \
LEMMY_PORT=8561 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_gamma" \
LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma \
LEMMY_SETUP__SITE_NAME=lemmy-gamma \
target/lemmy_server >/dev/null 2>&1 &
echo "start delta"
# An instance with only an allowlist for beta
LEMMY_HOSTNAME=lemmy-delta:8571 \
LEMMY_PORT=8571 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta \
LEMMY_SETUP__SITE_NAME=lemmy-delta \
target/lemmy_server >/dev/null 2>&1 &
echo "start epsilon"
# An instance who has a blocklist, with lemmy-alpha blocked
LEMMY_HOSTNAME=lemmy-epsilon:8581 \
LEMMY_PORT=8581 \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \
LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha \
LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon \
LEMMY_SETUP__SITE_NAME=lemmy-epsilon \
target/lemmy_server >/dev/null 2>&1 &
echo "wait for all instances to start"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8541/api/v2/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8551/api/v2/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8561/api/v2/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8571/api/v2/site')" != "200" ]]; do sleep 1; done
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8581/api/v2/site')" != "200" ]]; do sleep 1; done

20
api_tests/run-federation-test.sh

@ -0,0 +1,20 @@
#!/bin/bash
set -e
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432
pushd ..
cargo +1.47.0 build
rm target/lemmy_server || true
cp target/debug/lemmy_server target/lemmy_server
./api_tests/prepare-drone-federation-test.sh
popd
yarn
yarn api-test || true
killall lemmy_server
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE"
done

347
api_tests/src/comment.spec.ts

@ -11,7 +11,7 @@ import {
followBeta,
searchForBetaCommunity,
createComment,
updateComment,
editComment,
deleteComment,
removeComment,
getMentions,
@ -20,12 +20,8 @@ import {
createCommunity,
registerUser,
API,
delay,
longDelay,
} from './shared';
import {
Comment,
} from 'lemmy-js-client';
import { CommentView } from 'lemmy-js-client';
import { PostResponse } from 'lemmy-js-client';
@ -36,10 +32,9 @@ beforeAll(async () => {
await followBeta(alpha);
await followBeta(gamma);
let search = await searchForBetaCommunity(alpha);
await longDelay();
postRes = await createPost(
alpha,
search.communities.filter(c => c.local == false)[0].id
search.communities.find(c => c.community.local == false).community.id
);
});
@ -49,34 +44,34 @@ afterAll(async () => {
});
function assertCommentFederation(
commentOne: Comment,
commentTwo: Comment) {
expect(commentOne.ap_id).toBe(commentOne.ap_id);
expect(commentOne.content).toBe(commentTwo.content);
expect(commentOne.creator_name).toBe(commentTwo.creator_name);
expect(commentOne.community_actor_id).toBe(commentTwo.community_actor_id);
expect(commentOne.published).toBe(commentTwo.published);
expect(commentOne.updated).toBe(commentOne.updated);
expect(commentOne.deleted).toBe(commentOne.deleted);
expect(commentOne.removed).toBe(commentOne.removed);
commentOne: CommentView,
commentTwo: CommentView
) {
expect(commentOne.comment.ap_id).toBe(commentOne.comment.ap_id);
expect(commentOne.comment.content).toBe(commentTwo.comment.content);
expect(commentOne.creator.name).toBe(commentTwo.creator.name);
expect(commentOne.community.actor_id).toBe(commentTwo.community.actor_id);
expect(commentOne.comment.published).toBe(commentTwo.comment.published);
expect(commentOne.comment.updated).toBe(commentOne.comment.updated);
expect(commentOne.comment.deleted).toBe(commentOne.comment.deleted);
expect(commentOne.comment.removed).toBe(commentOne.comment.removed);
}
test('Create a comment', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
expect(commentRes.comment.content).toBeDefined();
expect(commentRes.comment.community_local).toBe(false);
expect(commentRes.comment.creator_local).toBe(true);
expect(commentRes.comment.score).toBe(1);
await longDelay();
let commentRes = await createComment(alpha, postRes.post_view.post.id);
expect(commentRes.comment_view.comment.content).toBeDefined();
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
// Make sure that comment is liked on beta
let searchBeta = await searchComment(beta, commentRes.comment);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
let betaComment = searchBeta.comments[0];
expect(betaComment).toBeDefined();
expect(betaComment.community_local).toBe(true);
expect(betaComment.creator_local).toBe(false);
expect(betaComment.score).toBe(1);
assertCommentFederation(betaComment, commentRes.comment);
expect(betaComment.community.local).toBe(true);
expect(betaComment.creator.local).toBe(false);
expect(betaComment.counts.score).toBe(1);
assertCommentFederation(betaComment, commentRes.comment_view);
});
test('Create a comment in a non-existent post', async () => {
@ -85,83 +80,90 @@ test('Create a comment in a non-existent post', async () => {
});
test('Update a comment', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
let commentRes = await createComment(alpha, postRes.post_view.post.id);
// Federate the comment first
let searchBeta = await searchComment(beta, commentRes.comment);
assertCommentFederation(searchBeta.comments[0], commentRes.comment);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
assertCommentFederation(searchBeta.comments[0], commentRes.comment_view);
await delay();
let updateCommentRes = await updateComment(alpha, commentRes.comment.id);
expect(updateCommentRes.comment.content).toBe(
let updateCommentRes = await editComment(
alpha,
commentRes.comment_view.comment.id
);
expect(updateCommentRes.comment_view.comment.content).toBe(
'A jest test federated comment update'
);
expect(updateCommentRes.comment.community_local).toBe(false);
expect(updateCommentRes.comment.creator_local).toBe(true);
await delay();
expect(updateCommentRes.comment_view.community.local).toBe(false);
expect(updateCommentRes.comment_view.creator.local).toBe(true);
// Make sure that post is updated on beta
let searchBetaUpdated = await searchComment(beta, commentRes.comment);
assertCommentFederation(searchBetaUpdated.comments[0], updateCommentRes.comment);
let searchBetaUpdated = await searchComment(
beta,
commentRes.comment_view.comment
);
assertCommentFederation(
searchBetaUpdated.comments[0],
updateCommentRes.comment_view
);
});
test('Delete a comment', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
await delay();
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let deleteCommentRes = await deleteComment(
alpha,
true,
commentRes.comment.id
commentRes.comment_view.comment.id
);
expect(deleteCommentRes.comment.deleted).toBe(true);
await delay();
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
// Make sure that comment is undefined on beta
let searchBeta = await searchComment(beta, commentRes.comment);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
let betaComment = searchBeta.comments[0];
expect(betaComment).toBeUndefined();
await delay();
let undeleteCommentRes = await deleteComment(
alpha,
false,
commentRes.comment.id
commentRes.comment_view.comment.id
);
expect(undeleteCommentRes.comment.deleted).toBe(false);
await delay();
expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
// Make sure that comment is undeleted on beta
let searchBeta2 = await searchComment(beta, commentRes.comment);
let searchBeta2 = await searchComment(beta, commentRes.comment_view.comment);
let betaComment2 = searchBeta2.comments[0];
expect(betaComment2.deleted).toBe(false);
assertCommentFederation(searchBeta2.comments[0], undeleteCommentRes.comment);
expect(betaComment2.comment.deleted).toBe(false);
assertCommentFederation(
searchBeta2.comments[0],
undeleteCommentRes.comment_view
);
});
test('Remove a comment from admin and community on the same instance', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
await delay();
let commentRes = await createComment(alpha, postRes.post_view.post.id);
// Get the id for beta
let betaCommentId = (await searchComment(beta, commentRes.comment))
.comments[0].id;
let betaCommentId = (
await searchComment(beta, commentRes.comment_view.comment)
).comments[0].comment.id;
// The beta admin removes it (the community lives on beta)
let removeCommentRes = await removeComment(beta, true, betaCommentId);
expect(removeCommentRes.comment.removed).toBe(true);
await longDelay();
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
let refetchedPost = await getPost(alpha, postRes.post.id);
expect(refetchedPost.comments[0].removed).toBe(true);
let refetchedPost = await getPost(alpha, postRes.post_view.post.id);
expect(refetchedPost.comments[0].comment.removed).toBe(true);
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
expect(unremoveCommentRes.comment.removed).toBe(false);
await longDelay();
expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
// Make sure that comment is unremoved on beta
let refetchedPost2 = await getPost(alpha, postRes.post.id);
expect(refetchedPost2.comments[0].removed).toBe(false);
assertCommentFederation(refetchedPost2.comments[0], unremoveCommentRes.comment);
let refetchedPost2 = await getPost(alpha, postRes.post_view.post.id);
expect(refetchedPost2.comments[0].comment.removed).toBe(false);
assertCommentFederation(
refetchedPost2.comments[0],
unremoveCommentRes.comment_view
);
});
test('Remove a comment from admin and community on different instance', async () => {
@ -173,160 +175,155 @@ test('Remove a comment from admin and community on different instance', async ()
// New alpha user creates a community, post, and comment.
let newCommunity = await createCommunity(newAlphaApi);
await delay();
let newPost = await createPost(newAlphaApi, newCommunity.community.id);
await delay();
let commentRes = await createComment(newAlphaApi, newPost.post.id);
expect(commentRes.comment.content).toBeDefined();
await delay();
let newPost = await createPost(
newAlphaApi,
newCommunity.community_view.community.id
);
let commentRes = await createComment(newAlphaApi, newPost.post_view.post.id);
expect(commentRes.comment_view.comment.content).toBeDefined();
// Beta searches that to cache it, then removes it
let searchBeta = await searchComment(beta, commentRes.comment);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
let betaComment = searchBeta.comments[0];
let removeCommentRes = await removeComment(beta, true, betaComment.id);
expect(removeCommentRes.comment.removed).toBe(true);
await delay();
let removeCommentRes = await removeComment(
beta,
true,
betaComment.comment.id
);
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
// Make sure its not removed on alpha
let refetchedPost = await getPost(newAlphaApi, newPost.post.id);
expect(refetchedPost.comments[0].removed).toBe(false);
assertCommentFederation(refetchedPost.comments[0], commentRes.comment);
let refetchedPost = await getPost(newAlphaApi, newPost.post_view.post.id);
expect(refetchedPost.comments[0].comment.removed).toBe(false);
assertCommentFederation(refetchedPost.comments[0], commentRes.comment_view);
});
test('Unlike a comment', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
await delay();
let unlike = await likeComment(alpha, 0, commentRes.comment);
expect(unlike.comment.score).toBe(0);
await delay();
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
expect(unlike.comment_view.counts.score).toBe(0);
// Make sure that post is unliked on beta
let searchBeta = await searchComment(beta, commentRes.comment);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
let betaComment = searchBeta.comments[0];
expect(betaComment).toBeDefined();
expect(betaComment.community_local).toBe(true);
expect(betaComment.creator_local).toBe(false);
expect(betaComment.score).toBe(0);
expect(betaComment.community.local).toBe(true);
expect(betaComment.creator.local).toBe(false);
expect(betaComment.counts.score).toBe(0);
});
test('Federated comment like', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
await longDelay();
let commentRes = await createComment(alpha, postRes.post_view.post.id);
// Find the comment on beta
let searchBeta = await searchComment(beta, commentRes.comment);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
let betaComment = searchBeta.comments[0];
let like = await likeComment(beta, 1, betaComment);
expect(like.comment.score).toBe(2);
await longDelay();
let like = await likeComment(beta, 1, betaComment.comment);
expect(like.comment_view.counts.score).toBe(2);
// Get the post from alpha, check the likes
let post = await getPost(alpha, postRes.post.id);
expect(post.comments[0].score).toBe(2);
let post = await getPost(alpha, postRes.post_view.post.id);
expect(post.comments[0].counts.score).toBe(2);
});
test('Reply to a comment', async () => {
// Create a comment on alpha, find it on beta
let commentRes = await createComment(alpha, postRes.post.id);
await delay();
let searchBeta = await searchComment(beta, commentRes.comment);
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
let betaComment = searchBeta.comments[0];
// find that comment id on beta
// Reply from beta
let replyRes = await createComment(beta, betaComment.post_id, betaComment.id);
expect(replyRes.comment.content).toBeDefined();
expect(replyRes.comment.community_local).toBe(true);
expect(replyRes.comment.creator_local).toBe(true);
expect(replyRes.comment.parent_id).toBe(betaComment.id);
expect(replyRes.comment.score).toBe(1);
await longDelay();
let replyRes = await createComment(
beta,
betaComment.post.id,
betaComment.comment.id
);
expect(replyRes.comment_view.comment.content).toBeDefined();
expect(replyRes.comment_view.community.local).toBe(true);
expect(replyRes.comment_view.creator.local).toBe(true);
expect(replyRes.comment_view.comment.parent_id).toBe(betaComment.comment.id);
expect(replyRes.comment_view.counts.score).toBe(1);
// Make sure that comment is seen on alpha
// TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
// comment, isn't working.
// let searchAlpha = await searchComment(alpha, replyRes.comment);
let post = await getPost(alpha, postRes.post.id);
let post = await getPost(alpha, postRes.post_view.post.id);
let alphaComment = post.comments[0];
expect(alphaComment.content).toBeDefined();
expect(alphaComment.parent_id).toBe(post.comments[1].id);
expect(alphaComment.community_local).toBe(false);
expect(alphaComment.creator_local).toBe(false);
expect(alphaComment.score).toBe(1);
assertCommentFederation(alphaComment, replyRes.comment);
expect(alphaComment.comment.content).toBeDefined();
expect(alphaComment.comment.parent_id).toBe(post.comments[1].comment.id);
expect(alphaComment.community.local).toBe(false);
expect(alphaComment.creator.local).toBe(false);
expect(alphaComment.counts.score).toBe(1);
assertCommentFederation(alphaComment, replyRes.comment_view);
});
test('Mention beta', async () => {
// Create a mention on alpha
let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8551';
let commentRes = await createComment(alpha, postRes.post.id);
await delay();
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let mentionRes = await createComment(
alpha,
postRes.post.id,
commentRes.comment.id,
postRes.post_view.post.id,
commentRes.comment_view.comment.id,
mentionContent
);
expect(mentionRes.comment.content).toBeDefined();
expect(mentionRes.comment.community_local).toBe(false);
expect(mentionRes.comment.creator_local).toBe(true);
expect(mentionRes.comment.score).toBe(1);
await delay();
expect(mentionRes.comment_view.comment.content).toBeDefined();
expect(mentionRes.comment_view.community.local).toBe(false);
expect(mentionRes.comment_view.creator.local).toBe(true);
expect(mentionRes.comment_view.counts.score).toBe(1);
let mentionsRes = await getMentions(beta);
expect(mentionsRes.mentions[0].content).toBeDefined();
expect(mentionsRes.mentions[0].community_local).toBe(true);
expect(mentionsRes.mentions[0].creator_local).toBe(false);
expect(mentionsRes.mentions[0].score).toBe(1);
expect(mentionsRes.mentions[0].comment.content).toBeDefined();
expect(mentionsRes.mentions[0].community.local).toBe(true);
expect(mentionsRes.mentions[0].creator.local).toBe(false);
expect(mentionsRes.mentions[0].counts.score).toBe(1);
});
test('Comment Search', async () => {
let commentRes = await createComment(alpha, postRes.post.id);
await delay();
let searchBeta = await searchComment(beta, commentRes.comment);
assertCommentFederation(searchBeta.comments[0], commentRes.comment);
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let searchBeta = await searchComment(beta, commentRes.comment_view.comment);
assertCommentFederation(searchBeta.comments[0], commentRes.comment_view);
});
test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => {
// Create a local post
let alphaPost = await createPost(alpha, 2);
expect(alphaPost.post.community_local).toBe(true);
await delay();
expect(alphaPost.post_view.community.local).toBe(true);
// Make sure gamma sees it
let search = await searchPost(gamma, alphaPost.post);
let search = await searchPost(gamma, alphaPost.post_view.post);
let gammaPost = search.posts[0];
let commentContent =
'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551';
let commentRes = await createComment(
gamma,
gammaPost.id,
gammaPost.post.id,
undefined,
commentContent
);
expect(commentRes.comment.content).toBe(commentContent);
expect(commentRes.comment.community_local).toBe(false);
expect(commentRes.comment.creator_local).toBe(true);
expect(commentRes.comment.score).toBe(1);
await longDelay();
expect(commentRes.comment_view.comment.content).toBe(commentContent);
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
// Make sure alpha sees it
let alphaPost2 = await getPost(alpha, alphaPost.post.id);
expect(alphaPost2.comments[0].content).toBe(commentContent);
expect(alphaPost2.comments[0].community_local).toBe(true);
expect(alphaPost2.comments[0].creator_local).toBe(false);
expect(alphaPost2.comments[0].score).toBe(1);
assertCommentFederation(alphaPost2.comments[0], commentRes.comment);
await delay();
let alphaPost2 = await getPost(alpha, alphaPost.post_view.post.id);
expect(alphaPost2.comments[0].comment.content).toBe(commentContent);
expect(alphaPost2.comments[0].community.local).toBe(true);
expect(alphaPost2.comments[0].creator.local).toBe(false);
expect(alphaPost2.comments[0].counts.score).toBe(1);
assertCommentFederation(alphaPost2.comments[0], commentRes.comment_view);
// Make sure beta has mentions
let mentionsRes = await getMentions(beta);
expect(mentionsRes.mentions[0].content).toBe(commentContent);
expect(mentionsRes.mentions[0].community_local).toBe(false);
expect(mentionsRes.mentions[0].creator_local).toBe(false);
expect(mentionsRes.mentions[0].comment.content).toBe(commentContent);
expect(mentionsRes.mentions[0].community.local).toBe(false);
expect(mentionsRes.mentions[0].creator.local).toBe(false);
// TODO this is failing because fetchInReplyTos aren't getting score
// expect(mentionsRes.mentions[0].score).toBe(1);
});
@ -335,60 +332,60 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
// Unfollow all remote communities
let followed = await unfollowRemotes(alpha);
expect(
followed.communities.filter(c => c.community_local == false).length
followed.communities.filter(c => c.community.local == false).length
).toBe(0);
// B creates a post, and two comments, should be invisible to A
let postRes = await createPost(beta, 2);
expect(postRes.post.name).toBeDefined();
await delay();
expect(postRes.post_view.post.name).toBeDefined();
let parentCommentContent = 'An invisible top level comment from beta';
let parentCommentRes = await createComment(
beta,
postRes.post.id,
postRes.post_view.post.id,
undefined,
parentCommentContent
);
expect(parentCommentRes.comment.content).toBe(parentCommentContent);
await delay();
expect(parentCommentRes.comment_view.comment.content).toBe(
parentCommentContent
);
// B creates a comment, then a child one of that.
let childCommentContent = 'An invisible child comment from beta';
let childCommentRes = await createComment(
beta,
postRes.post.id,
parentCommentRes.comment.id,
postRes.post_view.post.id,
parentCommentRes.comment_view.comment.id,
childCommentContent
);
expect(childCommentRes.comment_view.comment.content).toBe(
childCommentContent
);
expect(childCommentRes.comment.content).toBe(childCommentContent);
await delay();
// Follow beta again
let follow = await followBeta(alpha);
expect(follow.community.local).toBe(false);
expect(follow.community.name).toBe('main');
await delay();
expect(follow.community_view.community.local).toBe(false);
expect(follow.community_view.community.name).toBe('main');
// An update to the child comment on beta, should push the post, parent, and child to alpha now
let updatedCommentContent = 'An update child comment from beta';
let updateRes = await updateComment(
let updateRes = await editComment(
beta,
childCommentRes.comment.id,
childCommentRes.comment_view.comment.id,
updatedCommentContent
);
expect(updateRes.comment.content).toBe(updatedCommentContent);
await delay();
expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
// Get the post from alpha
let search = await searchPost(alpha, postRes.post);
let search = await searchPost(alpha, postRes.post_view.post);
let alphaPostB = search.posts[0];
await longDelay();
let alphaPost = await getPost(alpha, alphaPostB.id);
expect(alphaPost.post.name).toBeDefined();
assertCommentFederation(alphaPost.comments[1], parentCommentRes.comment);
assertCommentFederation(alphaPost.comments[0], updateRes.comment);
expect(alphaPost.post.community_local).toBe(false);
expect(alphaPost.post.creator_local).toBe(false);
let alphaPost = await getPost(alpha, alphaPostB.post.id);
expect(alphaPost.post_view.post.name).toBeDefined();
assertCommentFederation(alphaPost.comments[1], parentCommentRes.comment_view);
assertCommentFederation(alphaPost.comments[0], updateRes.comment_view);
expect(alphaPost.post_view.community.local).toBe(false);
expect(alphaPost.post_view.creator.local).toBe(false);
await unfollowRemotes(alpha);
});

142
api_tests/src/community.spec.ts

@ -3,155 +3,167 @@ import {
alpha,
beta,
setupLogins,
searchForBetaCommunity,
searchForCommunity,
createCommunity,
deleteCommunity,
removeCommunity,
getCommunity,
followCommunity,
delay,
longDelay,
} from './shared';
import {
Community,
} from 'lemmy-js-client';
import { CommunityView } from 'lemmy-js-client';
beforeAll(async () => {
await setupLogins();
});
function assertCommunityFederation(
communityOne: Community,
communityTwo: Community) {
expect(communityOne.actor_id).toBe(communityTwo.actor_id);
expect(communityOne.name).toBe(communityTwo.name);
expect(communityOne.title).toBe(communityTwo.title);
expect(communityOne.description).toBe(communityTwo.description);
expect(communityOne.icon).toBe(communityTwo.icon);
expect(communityOne.banner).toBe(communityTwo.banner);
expect(communityOne.published).toBe(communityTwo.published);
expect(communityOne.creator_actor_id).toBe(communityTwo.creator_actor_id);
expect(communityOne.nsfw).toBe(communityTwo.nsfw);
expect(communityOne.category_id).toBe(communityTwo.category_id);
expect(communityOne.removed).toBe(communityTwo.removed);
expect(communityOne.deleted).toBe(communityTwo.deleted);
communityOne: CommunityView,
communityTwo: CommunityView
) {
expect(communityOne.community.actor_id).toBe(communityTwo.community.actor_id);
expect(communityOne.community.name).toBe(communityTwo.community.name);
expect(communityOne.community.title).toBe(communityTwo.community.title);
expect(communityOne.community.description).toBe(
communityTwo.community.description
);
expect(communityOne.community.icon).toBe(communityTwo.community.icon);
expect(communityOne.community.banner).toBe(communityTwo.community.banner);
expect(communityOne.community.published).toBe(
communityTwo.community.published
);
expect(communityOne.creator.actor_id).toBe(communityTwo.creator.actor_id);
expect(communityOne.community.nsfw).toBe(communityTwo.community.nsfw);
expect(communityOne.community.category_id).toBe(
communityTwo.community.category_id
);
expect(communityOne.community.removed).toBe(communityTwo.community.removed);
expect(communityOne.community.deleted).toBe(communityTwo.community.deleted);
}
test('Create community', async () => {
let communityRes = await createCommunity(alpha);
expect(communityRes.community.name).toBeDefined();
expect(communityRes.community_view.community.name).toBeDefined();
// A dupe check
let prevName = communityRes.community.name;
let communityRes2 = await createCommunity(alpha, prevName);
let prevName = communityRes.community_view.community.name;
let communityRes2: any = await createCommunity(alpha, prevName);
expect(communityRes2['error']).toBe('community_already_exists');
await delay();
// Cache the community on beta, make sure it has the other fields
let searchShort = `!${prevName}@lemmy-alpha:8541`;
let search = await searchForCommunity(beta, searchShort);
let communityOnBeta = search.communities[0];
assertCommunityFederation(communityOnBeta, communityRes.community);
assertCommunityFederation(communityOnBeta, communityRes.community_view);
});
test('Delete community', async () => {
let communityRes = await createCommunity(beta);
await delay();
// Cache the community on Alpha
let searchShort = `!${communityRes.community.name}@lemmy-beta:8551`;
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
let search = await searchForCommunity(alpha, searchShort);
let communityOnAlpha = search.communities[0];
assertCommunityFederation(communityOnAlpha, communityRes.community);
await delay();
assertCommunityFederation(communityOnAlpha, communityRes.community_view);
// Follow the community from alpha
let follow = await followCommunity(alpha, true, communityOnAlpha.id);
let follow = await followCommunity(
alpha,
true,
communityOnAlpha.community.id
);
// Make sure the follow response went through
expect(follow.community.local).toBe(false);
await delay();
expect(follow.community_view.community.local).toBe(false);
let deleteCommunityRes = await deleteCommunity(
beta,
true,
communityRes.community.id
communityRes.community_view.community.id
);
expect(deleteCommunityRes.community.deleted).toBe(true);
await delay();
expect(deleteCommunityRes.community_view.community.deleted).toBe(true);
// Make sure it got deleted on A
let communityOnAlphaDeleted = await getCommunity(alpha, communityOnAlpha.id);
expect(communityOnAlphaDeleted.community.deleted).toBe(true);
await delay();
let communityOnAlphaDeleted = await getCommunity(
alpha,
communityOnAlpha.community.id
);
expect(communityOnAlphaDeleted.community_view.community.deleted).toBe(true);
// Undelete
let undeleteCommunityRes = await deleteCommunity(
beta,
false,
communityRes.community.id
communityRes.community_view.community.id
);
expect(undeleteCommunityRes.community.deleted).toBe(false);
await delay();
expect(undeleteCommunityRes.community_view.community.deleted).toBe(false);
// Make sure it got undeleted on A
let communityOnAlphaUnDeleted = await getCommunity(alpha, communityOnAlpha.id);
expect(communityOnAlphaUnDeleted.community.deleted).toBe(false);
let communityOnAlphaUnDeleted = await getCommunity(
alpha,
communityOnAlpha.community.id
);
expect(communityOnAlphaUnDeleted.community_view.community.deleted).toBe(
false
);
});
test('Remove community', async () => {
let communityRes = await createCommunity(beta);
await delay();
// Cache the community on Alpha
let searchShort = `!${communityRes.community.name}@lemmy-beta:8551`;
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
let search = await searchForCommunity(alpha, searchShort);
let communityOnAlpha = search.communities[0];
assertCommunityFederation(communityOnAlpha, communityRes.community);
await delay();
assertCommunityFederation(communityOnAlpha, communityRes.community_view);
// Follow the community from alpha
let follow = await followCommunity(alpha, true, communityOnAlpha.id);
let follow = await followCommunity(
alpha,
true,
communityOnAlpha.community.id
);
// Make sure the follow response went through
expect(follow.community.local).toBe(false);
await delay();
expect(follow.community_view.community.local).toBe(false);
let removeCommunityRes = await removeCommunity(
beta,
true,
communityRes.community.id
communityRes.community_view.community.id
);
expect(removeCommunityRes.community.removed).toBe(true);
await delay();
expect(removeCommunityRes.community_view.community.removed).toBe(true);
// Make sure it got Removed on A
let communityOnAlphaRemoved = await getCommunity(alpha, communityOnAlpha.id);
expect(communityOnAlphaRemoved.community.removed).toBe(true);
await delay();
let communityOnAlphaRemoved = await getCommunity(
alpha,
communityOnAlpha.community.id
);
expect(communityOnAlphaRemoved.community_view.community.removed).toBe(true);
// unremove
let unremoveCommunityRes = await removeCommunity(
beta,
false,
communityRes.community.id
communityRes.community_view.community.id
);
expect(unremoveCommunityRes.community.removed).toBe(false);
await delay();
expect(unremoveCommunityRes.community_view.community.removed).toBe(false);
// Make sure it got undeleted on A
let communityOnAlphaUnRemoved = await getCommunity(alpha, communityOnAlpha.id);
expect(communityOnAlphaUnRemoved.community.removed).toBe(false);
let communityOnAlphaUnRemoved = await getCommunity(
alpha,
communityOnAlpha.community.id
);
expect(communityOnAlphaUnRemoved.community_view.community.removed).toBe(
false
);
});
test('Search for beta community', async () => {
let communityRes = await createCommunity(beta);
expect(communityRes.community.name).toBeDefined();
await delay();
expect(communityRes.community_view.community.name).toBeDefined();
let searchShort = `!${communityRes.community.name}@lemmy-beta:8551`;
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
let search = await searchForCommunity(alpha, searchShort);
let communityOnAlpha = search.communities[0];
assertCommunityFederation(communityOnAlpha, communityRes.community);
assertCommunityFederation(communityOnAlpha, communityRes.community_view);
});

23
api_tests/src/follow.spec.ts

@ -6,8 +6,6 @@ import {
followCommunity,
checkFollowedCommunities,
unfollowRemotes,
delay,
longDelay,
} from './shared';
beforeAll(async () => {
@ -20,25 +18,26 @@ afterAll(async () => {
test('Follow federated community', async () => {
let search = await searchForBetaCommunity(alpha); // TODO sometimes this is returning null?
let follow = await followCommunity(alpha, true, search.communities[0].id);
let follow = await followCommunity(
alpha,
true,
search.communities[0].community.id
);
// Make sure the follow response went through