Add api tests for image endpoints (#4150)

* Add api tests for image endpoints (fixes #4105)

* curl instead of wget

* add missing files

* revert cargo update

* simplify setup

* use const

* rename to image.spec.ts

* adjust to client changes

* update client lib

* remove todos, move import

* try to fix ci

---------

Co-authored-by: SleeplessOne1917 <insomnia_void@protonmail.com>
This commit is contained in:
Nutomic 2023-11-17 05:43:40 +01:00 committed by GitHub
parent 7cb20200d8
commit 525359f7c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 126 additions and 62 deletions

2
.gitignore vendored
View File

@ -20,6 +20,8 @@ query_testing/**/reports/*.json
api_tests/node_modules api_tests/node_modules
api_tests/.yalc api_tests/.yalc
api_tests/yalc.lock api_tests/yalc.lock
api_tests/test.png
api_tests/pict-rs
# pictrs data # pictrs data
pictrs/ pictrs/

16
Cargo.lock generated
View File

@ -1997,9 +1997,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@ -3306,9 +3306,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.57" version = "0.10.59"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
dependencies = [ dependencies = [
"bitflags 2.4.1", "bitflags 2.4.1",
"cfg-if", "cfg-if",
@ -3338,9 +3338,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.93" version = "0.9.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -5356,9 +5356,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.9" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",

View File

@ -9,22 +9,24 @@
"scripts": { "scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'", "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
"fix": "prettier --write src && eslint --fix src", "fix": "prettier --write src && eslint --fix src",
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts", "api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts && jest -i image.spec.ts",
"api-test-comment": "jest -i comment.spec.ts", "api-test-comment": "jest -i comment.spec.ts",
"api-test-post": "jest -i post.spec.ts", "api-test-post": "jest -i post.spec.ts",
"api-test-user": "jest -i user.spec.ts", "api-test-user": "jest -i user.spec.ts",
"api-test-community": "jest -i community.spec.ts", "api-test-community": "jest -i community.spec.ts",
"api-test-private-message": "jest -i private_message.spec.ts" "api-test-private-message": "jest -i private_message.spec.ts",
"api-test-image": "jest -i image.spec.ts"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.8", "@types/jest": "^29.5.8",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
"@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0", "@typescript-eslint/parser": "^6.10.0",
"download-file-sync": "^1.0.4",
"eslint": "^8.53.0", "eslint": "^8.53.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"jest": "^29.5.0", "jest": "^29.5.0",
"lemmy-js-client": "0.19.0-rc.12", "lemmy-js-client": "0.19.0-alpha.18",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"typescript": "^5.0.4" "typescript": "^5.0.4"

View File

@ -8,6 +8,17 @@ export RUST_LOG="warn,lemmy_server=debug,lemmy_federate=debug,lemmy_api=debug,le
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
# pictrs setup
if ! [ -f "pict-rs" ]; then
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
chmod +x api_tests/pict-rs
fi
./api_tests/pict-rs \
run -a 0.0.0.0:8080 \
--danger-dummy-mode \
filesystem -p /tmp/pictrs/files \
sled -p /tmp/pictrs/sled-repo 2>&1 &
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
echo "DB URL: ${LEMMY_DATABASE_URL} INSTANCE: $INSTANCE" echo "DB URL: ${LEMMY_DATABASE_URL} INSTANCE: $INSTANCE"
psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE" psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE"

View File

@ -14,6 +14,8 @@ yarn
yarn api-test || true yarn api-test || true
killall -s1 lemmy_server || true killall -s1 lemmy_server || true
killall -s1 pict-rs || true
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE" psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE"
done done
rm -r /tmp/pictrs

View File

@ -54,8 +54,8 @@ beforeAll(async () => {
} }
}); });
afterAll(async () => { afterAll(() => {
await unfollows(); unfollows();
}); });
function assertCommentFederation( function assertCommentFederation(
@ -94,7 +94,9 @@ test("Create a comment", async () => {
}); });
test("Create a comment in a non-existent post", async () => { test("Create a comment in a non-existent post", async () => {
await expect(createComment(alpha, -1)).rejects.toBe("couldnt_find_post"); await expect(createComment(alpha, -1)).rejects.toStrictEqual(
Error("couldnt_find_post"),
);
}); });
test("Update a comment", async () => { test("Update a comment", async () => {
@ -143,7 +145,7 @@ test("Delete a comment", async () => {
await waitUntil( await waitUntil(
() => () =>
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
r => r !== "couldnt_find_object", r => r.message !== "couldnt_find_object",
) )
).comment; ).comment;
if (!gammaComment) { if (!gammaComment) {
@ -160,13 +162,13 @@ test("Delete a comment", async () => {
// Make sure that comment is undefined on beta // Make sure that comment is undefined on beta
await waitUntil( await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), () => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
e => e === "couldnt_find_object", e => e.message == "couldnt_find_object",
); );
// Make sure that comment is undefined on gamma after delete // Make sure that comment is undefined on gamma after delete
await waitUntil( await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), () => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
e => e === "couldnt_find_object", e => e.message === "couldnt_find_object",
); );
// Test undeleting the comment // Test undeleting the comment
@ -181,7 +183,7 @@ test("Delete a comment", async () => {
let betaComment2 = ( let betaComment2 = (
await waitUntil( await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), () => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
e => e !== "couldnt_find_object", e => e.message !== "couldnt_find_object",
) )
).comment; ).comment;
expect(betaComment2?.comment.deleted).toBe(false); expect(betaComment2?.comment.deleted).toBe(false);

View File

@ -34,9 +34,7 @@ import {
} from "./shared"; } from "./shared";
import { EditSite, LemmyHttp } from "lemmy-js-client"; import { EditSite, LemmyHttp } from "lemmy-js-client";
beforeAll(async () => { beforeAll(setupLogins);
await setupLogins();
});
function assertCommunityFederation( function assertCommunityFederation(
communityOne?: CommunityView, communityOne?: CommunityView,
@ -66,8 +64,8 @@ test("Create community", async () => {
// A dupe check // A dupe check
let prevName = communityRes.community_view.community.name; let prevName = communityRes.community_view.community.name;
await expect(createCommunity(alpha, prevName)).rejects.toBe( await expect(createCommunity(alpha, prevName)).rejects.toStrictEqual(
"community_already_exists", Error("community_already_exists"),
); );
// Cache the community on beta, make sure it has the other fields // Cache the community on beta, make sure it has the other fields
@ -333,8 +331,8 @@ test("Get community for different casing on domain", async () => {
// A dupe check // A dupe check
let prevName = communityRes.community_view.community.name; let prevName = communityRes.community_view.community.name;
await expect(createCommunity(alpha, prevName)).rejects.toBe( await expect(createCommunity(alpha, prevName)).rejects.toStrictEqual(
"community_already_exists", Error("community_already_exists"),
); );
// Cache the community on beta, make sure it has the other fields // Cache the community on beta, make sure it has the other fields

View File

@ -10,12 +10,10 @@ import {
waitUntil, waitUntil,
} from "./shared"; } from "./shared";
beforeAll(async () => { beforeAll(setupLogins);
await setupLogins();
});
afterAll(async () => { afterAll(() => {
await unfollowRemotes(alpha); unfollowRemotes(alpha);
}); });
test("Follow federated community", async () => { test("Follow federated community", async () => {

View File

@ -0,0 +1,44 @@
jest.setTimeout(120000);
import { UploadImage, DeleteImage } from "lemmy-js-client";
import { alpha, setupLogins, unfollowRemotes } from "./shared";
import fs = require("fs");
const downloadFileSync = require("download-file-sync");
beforeAll(setupLogins);
afterAll(() => {
unfollowRemotes(alpha);
});
test("Upload image and delete it", async () => {
// upload test image
const upload_image = fs.readFileSync("test.png");
const upload_form: UploadImage = {
image: upload_image,
};
const upload = await alpha.uploadImage(upload_form);
console.log(upload);
expect(upload.files![0].file).toBeDefined();
expect(upload.files![0].delete_token).toBeDefined();
expect(upload.url).toBeDefined();
expect(upload.delete_url).toBeDefined();
// ensure that image download is working. theres probably a better way to do this
const content = downloadFileSync(upload.url);
expect(content.length).toBeGreaterThan(0);
// delete image
const delete_form: DeleteImage = {
token: upload.files![0].delete_token,
filename: upload.files![0].file,
};
const delete_ = await alpha.deleteImage(delete_form);
expect(delete_).toBe(true);
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
expect(content2).toBe("");
});
// TODO: add tests for image purging

View File

@ -50,8 +50,8 @@ beforeAll(async () => {
await unfollows(); await unfollows();
}); });
afterAll(async () => { afterAll(() => {
await unfollows(); unfollows();
}); });
function assertPostFederation(postOne?: PostView, postTwo?: PostView) { function assertPostFederation(postOne?: PostView, postTwo?: PostView) {
@ -96,18 +96,20 @@ test("Create a post", async () => {
assertPostFederation(betaPost, postRes.post_view); assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id // Delta only follows beta, so it should not see an alpha ap_id
await expect(resolvePost(delta, postRes.post_view.post)).rejects.toBe( await expect(
"couldnt_find_object", resolvePost(delta, postRes.post_view.post),
); ).rejects.toStrictEqual(Error("couldnt_find_object"));
// Epsilon has alpha blocked, it should not see the alpha post // Epsilon has alpha blocked, it should not see the alpha post
await expect(resolvePost(epsilon, postRes.post_view.post)).rejects.toBe( await expect(
"couldnt_find_object", resolvePost(epsilon, postRes.post_view.post),
); ).rejects.toStrictEqual(Error("couldnt_find_object"));
}); });
test("Create a post in a non-existent community", async () => { test("Create a post in a non-existent community", async () => {
await expect(createPost(alpha, -2)).rejects.toBe("couldnt_find_community"); await expect(createPost(alpha, -2)).rejects.toStrictEqual(
Error("couldnt_find_community"),
);
}); });
test("Unlike a post", async () => { test("Unlike a post", async () => {
@ -157,8 +159,8 @@ test("Update a post", async () => {
assertPostFederation(betaPost, updatedPost.post_view); assertPostFederation(betaPost, updatedPost.post_view);
// Make sure lemmy beta cannot update the post // Make sure lemmy beta cannot update the post
await expect(editPost(beta, betaPost.post)).rejects.toBe( await expect(editPost(beta, betaPost.post)).rejects.toStrictEqual(
"no_post_edit_allowed", Error("no_post_edit_allowed"),
); );
}); });
@ -226,7 +228,9 @@ test("Lock a post", async () => {
); );
// Try to make a new comment there, on alpha // Try to make a new comment there, on alpha
await expect(createComment(alpha, alphaPost1.post.id)).rejects.toBe("locked"); await expect(createComment(alpha, alphaPost1.post.id)).rejects.toStrictEqual(
Error("locked"),
);
// Unlock a post // Unlock a post
let unlockedPost = await lockPost(beta, false, betaPost1.post); let unlockedPost = await lockPost(beta, false, betaPost1.post);
@ -281,8 +285,8 @@ test("Delete a post", async () => {
assertPostFederation(betaPost2, undeletedPost.post_view); assertPostFederation(betaPost2, undeletedPost.post_view);
// Make sure lemmy beta cannot delete the post // Make sure lemmy beta cannot delete the post
await expect(deletePost(beta, true, betaPost2.post)).rejects.toBe( await expect(deletePost(beta, true, betaPost2.post)).rejects.toStrictEqual(
"no_post_edit_allowed", Error("no_post_edit_allowed"),
); );
}); });
@ -483,12 +487,12 @@ test.skip("Enforce community ban for federated user", async () => {
// ensure that the post by alpha got removed // ensure that the post by alpha got removed
await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe( await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe(
"unknown", Error("unknown"),
); );
// Alpha tries to make post on beta, but it fails because of ban // Alpha tries to make post on beta, but it fails because of ban
await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe( await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe(
"banned_from_community", Error("banned_from_community"),
); );
// Unban alpha // Unban alpha

View File

@ -20,8 +20,8 @@ beforeAll(async () => {
recipient_id = 3; recipient_id = 3;
}); });
afterAll(async () => { afterAll(() => {
await unfollowRemotes(alpha); unfollowRemotes(alpha);
}); });
test("Create a private message", async () => { test("Create a private message", async () => {

View File

@ -10,6 +10,7 @@ import {
InstanceId, InstanceId,
LemmyHttp, LemmyHttp,
PostView, PostView,
SuccessResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost"; import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
@ -58,7 +59,6 @@ import { Register } from "lemmy-js-client/dist/types/Register";
import { SaveUserSettings } from "lemmy-js-client/dist/types/SaveUserSettings"; import { SaveUserSettings } from "lemmy-js-client/dist/types/SaveUserSettings";
import { DeleteAccount } from "lemmy-js-client/dist/types/DeleteAccount"; import { DeleteAccount } from "lemmy-js-client/dist/types/DeleteAccount";
import { GetSiteResponse } from "lemmy-js-client/dist/types/GetSiteResponse"; import { GetSiteResponse } from "lemmy-js-client/dist/types/GetSiteResponse";
import { DeleteAccountResponse } from "lemmy-js-client/dist/types/DeleteAccountResponse";
import { PrivateMessagesResponse } from "lemmy-js-client/dist/types/PrivateMessagesResponse"; import { PrivateMessagesResponse } from "lemmy-js-client/dist/types/PrivateMessagesResponse";
import { GetPrivateMessages } from "lemmy-js-client/dist/types/GetPrivateMessages"; import { GetPrivateMessages } from "lemmy-js-client/dist/types/GetPrivateMessages";
import { PostReportResponse } from "lemmy-js-client/dist/types/PostReportResponse"; import { PostReportResponse } from "lemmy-js-client/dist/types/PostReportResponse";
@ -637,7 +637,7 @@ export async function loginUser(
export async function saveUserSettingsBio( export async function saveUserSettingsBio(
api: LemmyHttp, api: LemmyHttp,
): Promise<LoginResponse> { ): Promise<SuccessResponse> {
let form: SaveUserSettings = { let form: SaveUserSettings = {
show_nsfw: true, show_nsfw: true,
blur_nsfw: false, blur_nsfw: false,
@ -655,7 +655,7 @@ export async function saveUserSettingsBio(
export async function saveUserSettingsFederated( export async function saveUserSettingsFederated(
api: LemmyHttp, api: LemmyHttp,
): Promise<LoginResponse> { ): Promise<SuccessResponse> {
let avatar = "https://image.flaticon.com/icons/png/512/35/35896.png"; let avatar = "https://image.flaticon.com/icons/png/512/35/35896.png";
let banner = "https://image.flaticon.com/icons/png/512/36/35896.png"; let banner = "https://image.flaticon.com/icons/png/512/36/35896.png";
let bio = "a changed bio"; let bio = "a changed bio";
@ -679,7 +679,7 @@ export async function saveUserSettingsFederated(
export async function saveUserSettings( export async function saveUserSettings(
api: LemmyHttp, api: LemmyHttp,
form: SaveUserSettings, form: SaveUserSettings,
): Promise<LoginResponse> { ): Promise<SuccessResponse> {
return api.saveUserSettings(form); return api.saveUserSettings(form);
} }
export async function getPersonDetails( export async function getPersonDetails(
@ -692,9 +692,7 @@ export async function getPersonDetails(
return api.getPersonDetails(form); return api.getPersonDetails(form);
} }
export async function deleteUser( export async function deleteUser(api: LemmyHttp): Promise<SuccessResponse> {
api: LemmyHttp,
): Promise<DeleteAccountResponse> {
let form: DeleteAccount = { let form: DeleteAccount = {
delete_content: true, delete_content: true,
password, password,

View File

@ -22,9 +22,7 @@ import {
import { LemmyHttp, SaveUserSettings } from "lemmy-js-client"; import { LemmyHttp, SaveUserSettings } from "lemmy-js-client";
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts"; import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
beforeAll(async () => { beforeAll(setupLogins);
await setupLogins();
});
let apShortname: string; let apShortname: string;

BIN
api_tests/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1270,6 +1270,11 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
download-file-sync@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/download-file-sync/-/download-file-sync-1.0.4.tgz#d3e3c543f836f41039455b9034c72e355b036019"
integrity sha512-vH92qNH508jZZA12HQNq/aiMDfagr4JvjFiI17Bi8oYjsxwv5ZVIi7iHkYmUXxOQUr90tcVX+8EPePjAqG1Y0w==
electron-to-chromium@^1.4.535: electron-to-chromium@^1.4.535:
version "1.4.537" version "1.4.537"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz#aac4101db53066be1e49baedd000a26bc754adc9" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz#aac4101db53066be1e49baedd000a26bc754adc9"
@ -2281,10 +2286,10 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
lemmy-js-client@0.19.0-rc.12: lemmy-js-client@0.19.0-alpha.18:
version "0.19.0-rc.12" version "0.19.0-alpha.18"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.12.tgz#e3bd4e21b1966d583ab790ef70ece8394b012b48" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-alpha.18.tgz#f94841681cabdf9d5c4ce7048eacb57557f68724"
integrity sha512-1iu2fW9vlb3TrI+QR/ODP3+5pWZB0rUqL1wH09IzomDXohCqoQvfmXpwArmgF4Eq8GZgjkcfeMDC2gMrfw/i7Q== integrity sha512-cKJfKKnjK+ijk0Yd6ydtne3Y4FILp2RbQg05pCru9n6PCyPAa85eQL4QxPB1PPed20ckSZRcHLcnr/bYFDgpaw==
dependencies: dependencies:
cross-fetch "^3.1.5" cross-fetch "^3.1.5"
form-data "^4.0.0" form-data "^4.0.0"