mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
refactor(api): ♻️ Refactor all tests to use new client
This commit is contained in:
parent
b6373dc185
commit
54e282b03c
|
|
@ -1,9 +1,9 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let role: Role;
|
let role: Role;
|
||||||
let higherPriorityRole: Role;
|
let higherPriorityRole: Role;
|
||||||
|
|
||||||
|
|
@ -44,56 +44,44 @@ afterAll(async () => {
|
||||||
// /api/v1/accounts/:id/roles/:role_id
|
// /api/v1/accounts/:id/roles/:role_id
|
||||||
describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/accounts/${users[1].id}/roles/${role.id}`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.assignRole(users[1].id, role.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if role does not exist", async () => {
|
test("should return 404 if role does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[1].id}/roles/00000000-0000-0000-0000-000000000000`,
|
|
||||||
{
|
const { ok, raw } = await client.assignRole(
|
||||||
method: "POST",
|
users[1].id,
|
||||||
headers: {
|
"00000000-0000-0000-0000-000000000000",
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if user does not exist", async () => {
|
test("should return 404 if user does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/00000000-0000-0000-0000-000000000000/roles/${role.id}`,
|
|
||||||
{
|
const { ok, raw } = await client.assignRole(
|
||||||
method: "POST",
|
"00000000-0000-0000-0000-000000000000",
|
||||||
headers: {
|
role.id,
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should assign role to user", async () => {
|
test("should assign role to user", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[1].id}/roles/${role.id}`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
const { ok } = await client.assignRole(users[1].id, role.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
// Check if role was assigned
|
// Check if role was assigned
|
||||||
const userRoles = await Role.getUserRoles(users[1].id, false);
|
const userRoles = await Role.getUserRoles(users[1].id, false);
|
||||||
|
|
@ -103,19 +91,16 @@ describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 403 if user tries to assign role with higher priority", async () => {
|
test("should return 403 if user tries to assign role with higher priority", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[1].id}/roles/${higherPriorityRole.id}`,
|
|
||||||
{
|
const { data, ok, raw } = await client.assignRole(
|
||||||
method: "POST",
|
users[1].id,
|
||||||
headers: {
|
higherPriorityRole.id,
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Forbidden",
|
error: "Forbidden",
|
||||||
details:
|
details:
|
||||||
"User with highest role priority 2 cannot assign role with priority 3",
|
"User with highest role priority 2 cannot assign role with priority 3",
|
||||||
|
|
@ -123,17 +108,11 @@ describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should remove role from user", async () => {
|
test("should remove role from user", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[1].id}/roles/${role.id}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
const { ok } = await client.unassignRole(users[1].id, role.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
// Check if role was removed
|
// Check if role was removed
|
||||||
const userRoles = await Role.getUserRoles(users[1].id, false);
|
const userRoles = await Role.getUserRoles(users[1].id, false);
|
||||||
|
|
@ -145,19 +124,16 @@ describe("/api/v1/accounts/:id/roles/:role_id", () => {
|
||||||
test("should return 403 if user tries to remove role with higher priority", async () => {
|
test("should return 403 if user tries to remove role with higher priority", async () => {
|
||||||
await higherPriorityRole.linkUser(users[1].id);
|
await higherPriorityRole.linkUser(users[1].id);
|
||||||
|
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[1].id}/roles/${higherPriorityRole.id}`,
|
|
||||||
{
|
const { data, ok, raw } = await client.unassignRole(
|
||||||
method: "DELETE",
|
users[1].id,
|
||||||
headers: {
|
higherPriorityRole.id,
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Forbidden",
|
error: "Forbidden",
|
||||||
details:
|
details:
|
||||||
"User with highest role priority 2 cannot remove role with priority 3",
|
"User with highest role priority 2 cannot remove role with priority 3",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let role: Role;
|
let role: Role;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|
@ -29,37 +29,27 @@ afterAll(async () => {
|
||||||
|
|
||||||
describe("/api/v1/accounts/:id/roles", () => {
|
describe("/api/v1/accounts/:id/roles", () => {
|
||||||
test("should return 404 if user does not exist", async () => {
|
test("should return 404 if user does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/accounts/00000000-0000-0000-0000-000000000000/roles",
|
|
||||||
{
|
const { data, ok, raw } = await client.getAccountRoles(
|
||||||
method: "GET",
|
"00000000-0000-0000-0000-000000000000",
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(404);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "User not found",
|
error: "User not found",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return a list of roles for the user", async () => {
|
test("should return a list of roles for the user", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[0].id}/roles`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
const { data, ok } = await client.getAccountRoles(users[0].id);
|
||||||
const roles = await response.json();
|
|
||||||
expect(roles).toContainEqual({
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArray();
|
||||||
|
expect(data).toContainEqual({
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: "test",
|
name: "test",
|
||||||
permissions: [RolePermissions.ManageRoles],
|
permissions: [RolePermissions.ManageRoles],
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { db } from "@versia/kit/db";
|
import { db } from "@versia/kit/db";
|
||||||
import { Emojis } from "@versia/kit/tables";
|
import { Emojis } from "@versia/kit/tables";
|
||||||
import { inArray } from "drizzle-orm";
|
import { inArray } from "drizzle-orm";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let id = "";
|
let id = "";
|
||||||
|
|
||||||
// Make user 2 an admin
|
// Make user 2 an admin
|
||||||
|
|
@ -12,22 +12,14 @@ beforeAll(async () => {
|
||||||
await users[1].update({ isAdmin: true });
|
await users[1].update({ isAdmin: true });
|
||||||
|
|
||||||
// Create an emoji
|
// Create an emoji
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
await using client = await generateClient(users[1]);
|
||||||
headers: {
|
const { data, ok } = await client.uploadEmoji(
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
"test",
|
||||||
"Content-Type": "application/json",
|
new URL("https://cdn.versia.social/logo.webp"),
|
||||||
},
|
);
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
shortcode: "test",
|
|
||||||
element: "https://cdn.versia.social/logo.webp",
|
|
||||||
global: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const emoji = await response.json();
|
id = data.id;
|
||||||
id = emoji.id;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -41,124 +33,95 @@ afterAll(async () => {
|
||||||
// /api/v1/emojis/:id (PATCH, DELETE, GET)
|
// /api/v1/emojis/:id (PATCH, DELETE, GET)
|
||||||
describe("/api/v1/emojis/:id", () => {
|
describe("/api/v1/emojis/:id", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient();
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getEmoji(id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if emoji does not exist", async () => {
|
test("should return 404 if emoji does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
"/api/v1/emojis/00000000-0000-0000-0000-000000000000",
|
|
||||||
{
|
const { ok, raw } = await client.getEmoji(
|
||||||
headers: {
|
"00000000-0000-0000-0000-000000000000",
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not work if the user is trying to update an emoji they don't own", async () => {
|
test("should not work if the user is trying to update an emoji they don't own", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
const { ok, raw } = await client.updateEmoji(id, {
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method: "PATCH",
|
|
||||||
body: JSON.stringify({
|
|
||||||
shortcode: "test2",
|
shortcode: "test2",
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return the emoji", async () => {
|
test("should return the emoji", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
const { data, ok } = await client.getEmoji(id);
|
||||||
const emoji = await response.json();
|
|
||||||
expect(emoji.shortcode).toBe("test");
|
expect(ok).toBe(true);
|
||||||
|
expect(data.shortcode).toBe("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should update the emoji", async () => {
|
test("should update the emoji", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
const { data, ok } = await client.updateEmoji(id, {
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method: "PATCH",
|
|
||||||
body: JSON.stringify({
|
|
||||||
shortcode: "test2",
|
shortcode: "test2",
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const emoji = await response.json();
|
expect(data.shortcode).toBe("test2");
|
||||||
expect(emoji.shortcode).toBe("test2");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should update the emoji with another url, but keep the shortcode", async () => {
|
test("should update the emoji with another url, but keep the shortcode", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
const { data, ok } = await client.updateEmoji(id, {
|
||||||
"Content-Type": "application/json",
|
image: new URL(
|
||||||
},
|
"https://avatars.githubusercontent.com/u/30842467?v=4",
|
||||||
method: "PATCH",
|
),
|
||||||
body: JSON.stringify({
|
|
||||||
element: "https://avatars.githubusercontent.com/u/30842467?v=4",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const emoji = await response.json();
|
expect(data.shortcode).toBe("test2");
|
||||||
expect(emoji.shortcode).toBe("test2");
|
expect(data.url).toContain("/media/proxy/");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should update the emoji to be non-global", async () => {
|
test("should update the emoji to be non-global", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
const { data, ok } = await client.updateEmoji(id, {
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method: "PATCH",
|
|
||||||
body: JSON.stringify({
|
|
||||||
global: false,
|
global: false,
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
|
expect(data.global).toBe(false);
|
||||||
|
|
||||||
// Check if the other user can see it
|
// Check if the other user can see it
|
||||||
const response2 = await fakeRequest("/api/v1/custom_emojis", {
|
await using client2 = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response2.ok).toBe(true);
|
const { data: data2, ok: ok2 } =
|
||||||
const emojis = await response2.json();
|
await client2.getInstanceCustomEmojis();
|
||||||
expect(emojis).not.toContainEqual(expect.objectContaining({ id }));
|
|
||||||
|
expect(ok2).toBe(true);
|
||||||
|
expect(data2).not.toContainEqual(expect.objectContaining({ id }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should delete the emoji", async () => {
|
test("should delete the emoji", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/emojis/${id}`, {
|
await using client = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
const { ok } = await client.deleteEmoji(id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import { db } from "@versia/kit/db";
|
||||||
import { Emojis } from "@versia/kit/tables";
|
import { Emojis } from "@versia/kit/tables";
|
||||||
import { inArray } from "drizzle-orm";
|
import { inArray } from "drizzle-orm";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(3);
|
const { users, deleteUsers } = await getTestUsers(3);
|
||||||
|
|
||||||
// Make user 2 an admin
|
// Make user 2 an admin
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|
@ -39,146 +39,109 @@ const createImage = async (name: string): Promise<File> => {
|
||||||
|
|
||||||
describe("/api/v1/emojis", () => {
|
describe("/api/v1/emojis", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
await using client = await generateClient();
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
shortcode: "test",
|
|
||||||
element: "https://cdn.versia.social/logo.webp",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.uploadEmoji(
|
||||||
|
"test",
|
||||||
|
new URL("https://cdn.versia.social/logo.webp"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Admin tests", () => {
|
describe("Admin tests", () => {
|
||||||
test("should upload a file and create an emoji", async () => {
|
test("should upload a file and create an emoji", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[1]);
|
||||||
formData.append("shortcode", "test1");
|
|
||||||
formData.append("element", await createImage("test.png"));
|
|
||||||
formData.append("global", "true");
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
const { data, ok } = await client.uploadEmoji(
|
||||||
method: "POST",
|
"test1",
|
||||||
headers: {
|
await createImage("test.png"),
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
{
|
||||||
|
global: true,
|
||||||
},
|
},
|
||||||
body: formData,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const emoji = await response.json();
|
expect(data.shortcode).toBe("test1");
|
||||||
expect(emoji.shortcode).toBe("test1");
|
expect(data.url).toContain("/media/proxy");
|
||||||
expect(emoji.url).toContain("/media/proxy");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should try to upload a non-image", async () => {
|
test("should try to upload a non-image", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[1]);
|
||||||
formData.append("shortcode", "test2");
|
|
||||||
formData.append("element", new File(["test"], "test.txt"));
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
const { ok, raw } = await client.uploadEmoji(
|
||||||
method: "POST",
|
"test2",
|
||||||
headers: {
|
new File(["test"], "test.txt"),
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
);
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should upload an emoji by url", async () => {
|
test("should upload an emoji by url", async () => {
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
await using client = await generateClient(users[1]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
shortcode: "test3",
|
|
||||||
element: "https://cdn.versia.social/logo.webp",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
const { data, ok } = await client.uploadEmoji(
|
||||||
const emoji = await response.json();
|
"test3",
|
||||||
expect(emoji.shortcode).toBe("test3");
|
new URL("https://cdn.versia.social/logo.webp"),
|
||||||
expect(emoji.url).toContain("/media/proxy/");
|
);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data.shortcode).toBe("test3");
|
||||||
|
expect(data.url).toContain("/media/proxy");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should fail when uploading an already existing emoji", async () => {
|
test("should fail when uploading an already existing emoji", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[1]);
|
||||||
formData.append("shortcode", "test1");
|
|
||||||
formData.append("element", await createImage("test-image.png"));
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
const { ok, raw } = await client.uploadEmoji(
|
||||||
method: "POST",
|
"test1",
|
||||||
headers: {
|
await createImage("test-image.png"),
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
);
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("User tests", () => {
|
describe("User tests", () => {
|
||||||
test("should upload a file and create an emoji", async () => {
|
test("should upload a file and create an emoji", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[0]);
|
||||||
formData.append("shortcode", "test4");
|
|
||||||
formData.append("element", await createImage("test-image.png"));
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
const { data, ok } = await client.uploadEmoji(
|
||||||
method: "POST",
|
"test4",
|
||||||
headers: {
|
await createImage("test-image.png"),
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
);
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const emoji = await response.json();
|
expect(data.shortcode).toBe("test4");
|
||||||
expect(emoji.shortcode).toBe("test4");
|
expect(data.url).toContain("/media/proxy");
|
||||||
expect(emoji.url).toContain("/media/proxy/");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should fail when uploading an already existing global emoji", async () => {
|
test("should fail when uploading an already existing global emoji", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[0]);
|
||||||
formData.append("shortcode", "test1");
|
|
||||||
formData.append("element", await createImage("test-image.png"));
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
const { ok, raw } = await client.uploadEmoji(
|
||||||
method: "POST",
|
"test1",
|
||||||
headers: {
|
await createImage("test-image.png"),
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
);
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create an emoji as another user with the same shortcode", async () => {
|
test("should create an emoji as another user with the same shortcode", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[2]);
|
||||||
formData.append("shortcode", "test4");
|
|
||||||
formData.append("element", await createImage("test-image.png"));
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/emojis", {
|
const { data, ok } = await client.uploadEmoji(
|
||||||
method: "POST",
|
"test4",
|
||||||
headers: {
|
await createImage("test-image.png"),
|
||||||
Authorization: `Bearer ${tokens[2].data.accessToken}`,
|
);
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const emoji = await response.json();
|
expect(data.shortcode).toBe("test4");
|
||||||
expect(emoji.shortcode).toBe("test4");
|
expect(data.url).toContain("/media/proxy/");
|
||||||
expect(emoji.url).toContain("/media/proxy/");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest } from "~/tests/utils";
|
import { generateClient } from "~/tests/utils";
|
||||||
|
|
||||||
// /api/v1/instance/extended_description
|
// /api/v1/instance/extended_description
|
||||||
describe("/api/v1/instance/extended_description", () => {
|
describe("/api/v1/instance/extended_description", () => {
|
||||||
test("should return extended description", async () => {
|
test("should return extended description", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
"/api/v1/instance/extended_description",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getInstanceExtendedDescription();
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toEqual({
|
expect(data).toEqual({
|
||||||
updated_at: new Date(0).toISOString(),
|
updated_at: new Date(0).toISOString(),
|
||||||
content:
|
content:
|
||||||
'<p>This is a <a href="https://versia.pub">Versia</a> server with the default extended description.</p>\n',
|
'<p>This is a <a href="https://versia.pub">Versia</a> server with the default extended description.</p>\n',
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest } from "~/tests/utils";
|
import { generateClient } from "~/tests/utils";
|
||||||
|
|
||||||
// /api/v1/instance/privacy_policy
|
// /api/v1/instance/privacy_policy
|
||||||
describe("/api/v1/instance/privacy_policy", () => {
|
describe("/api/v1/instance/privacy_policy", () => {
|
||||||
test("should return privacy policy", async () => {
|
test("should return privacy policy", async () => {
|
||||||
const response = await fakeRequest("/api/v1/instance/privacy_policy");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getInstancePrivacyPolicy();
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toEqual({
|
expect(data).toEqual({
|
||||||
updated_at: new Date(0).toISOString(),
|
updated_at: new Date(0).toISOString(),
|
||||||
// This instance has not provided any privacy policy.
|
// This instance has not provided any privacy policy.
|
||||||
content:
|
content:
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
import { fakeRequest } from "~/tests/utils";
|
import { generateClient } from "~/tests/utils";
|
||||||
|
|
||||||
// /api/v1/instance/rules
|
// /api/v1/instance/rules
|
||||||
describe("/api/v1/instance/rules", () => {
|
describe("/api/v1/instance/rules", () => {
|
||||||
test("should return rules", async () => {
|
test("should return rules", async () => {
|
||||||
const response = await fakeRequest("/api/v1/instance/rules");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getRules();
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toEqual(
|
expect(data).toEqual(
|
||||||
config.instance.rules.map((r, index) => ({
|
config.instance.rules.map((r, index) => ({
|
||||||
id: String(index),
|
id: String(index),
|
||||||
text: r.text,
|
text: r.text,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest } from "~/tests/utils";
|
import { generateClient } from "~/tests/utils";
|
||||||
|
|
||||||
// /api/v1/instance/terms_of_service
|
// /api/v1/instance/terms_of_service
|
||||||
describe("/api/v1/instance/terms_of_service", () => {
|
describe("/api/v1/instance/terms_of_service", () => {
|
||||||
test("should return terms of service", async () => {
|
test("should return terms of service", async () => {
|
||||||
const response = await fakeRequest("/api/v1/instance/terms_of_service");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getInstanceTermsOfService();
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toEqual({
|
expect(data).toEqual({
|
||||||
updated_at: new Date(0).toISOString(),
|
updated_at: new Date(0).toISOString(),
|
||||||
// This instance has not provided any terms of service.
|
// This instance has not provided any terms of service.
|
||||||
content:
|
content:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(1);
|
const { users, deleteUsers } = await getTestUsers(1);
|
||||||
const timeline = await getTestStatuses(10, users[0]);
|
const timeline = await getTestStatuses(10, users[0]);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -11,46 +11,34 @@ afterAll(async () => {
|
||||||
// /api/v1/markers
|
// /api/v1/markers
|
||||||
describe("/api/v1/markers", () => {
|
describe("/api/v1/markers", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/markers", {
|
await using client = await generateClient();
|
||||||
method: "GET",
|
|
||||||
});
|
const { ok, raw } = await client.getMarkers(["home", "notifications"]);
|
||||||
expect(response.status).toBe(401);
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return empty markers", async () => {
|
test("should return empty markers", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/markers?${new URLSearchParams([
|
|
||||||
["timeline[]", "home"],
|
|
||||||
["timeline[]", "notifications"],
|
|
||||||
])}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getMarkers(["home", "notifications"]);
|
||||||
expect(await response.json()).toEqual({});
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create markers", async () => {
|
test("should create markers", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/markers?${new URLSearchParams({
|
|
||||||
"home[last_read_id]": timeline[0].id,
|
|
||||||
})}`,
|
|
||||||
|
|
||||||
{
|
const { data, ok } = await client.saveMarkers({
|
||||||
method: "POST",
|
home: {
|
||||||
headers: {
|
last_read_id: timeline[0].id,
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(await response.json()).toEqual({
|
expect(data).toEqual({
|
||||||
home: {
|
home: {
|
||||||
last_read_id: timeline[0].id,
|
last_read_id: timeline[0].id,
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
|
|
@ -60,21 +48,12 @@ describe("/api/v1/markers", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return markers", async () => {
|
test("should return markers", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/markers?${new URLSearchParams([
|
|
||||||
["timeline[]", "home"],
|
|
||||||
["timeline[]", "notifications"],
|
|
||||||
])}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getMarkers(["home", "notifications"]);
|
||||||
expect(await response.json()).toEqual({
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toEqual({
|
||||||
home: {
|
home: {
|
||||||
last_read_id: timeline[0].id,
|
last_read_id: timeline[0].id,
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,54 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(3);
|
const { users, deleteUsers } = await getTestUsers(3);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const response = await fakeRequest(`/api/v1/accounts/${users[1].id}/mute`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok } = await client.muteAccount(users[1].id);
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
expect(ok).toBe(true);
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v1/mutes
|
// /api/v1/mutes
|
||||||
describe("/api/v1/mutes", () => {
|
describe("/api/v1/mutes", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
"/api/v1/mutes".replace(":id", users[1].id),
|
|
||||||
{
|
const { ok, raw } = await client.getMutes();
|
||||||
method: "GET",
|
|
||||||
},
|
expect(ok).toBe(false);
|
||||||
);
|
expect(raw.status).toBe(401);
|
||||||
expect(response.status).toBe(401);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return mutes", async () => {
|
test("should return mutes", async () => {
|
||||||
const response = await fakeRequest("/api/v1/mutes", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "GET",
|
|
||||||
headers: {
|
const { data, ok } = await client.getMutes();
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
expect(ok).toBe(true);
|
||||||
});
|
expect(data).toEqual([
|
||||||
expect(response.status).toBe(200);
|
|
||||||
const body = await response.json();
|
|
||||||
expect(body).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: users[1].id,
|
id: users[1].id,
|
||||||
}),
|
}),
|
||||||
]),
|
]);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return mutes after unmute", async () => {
|
test("should return mutes after unmute", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/accounts/${users[1].id}/unmute`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
|
|
||||||
const response2 = await fakeRequest("/api/v1/mutes", {
|
const { ok } = await client.unmuteAccount(users[1].id);
|
||||||
method: "GET",
|
|
||||||
headers: {
|
expect(ok).toBe(true);
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
const { data, ok: ok2 } = await client.getMutes();
|
||||||
});
|
|
||||||
expect(response2.status).toBe(200);
|
expect(ok2).toBe(true);
|
||||||
const body = await response2.json();
|
expect(data).toEqual([]);
|
||||||
expect(body).toEqual([]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,24 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Notification as ApiNotification } from "@versia/client/types";
|
import type { z } from "@hono/zod-openapi";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import type { Notification } from "@versia/client-ng/schemas";
|
||||||
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let notifications: ApiNotification[] = [];
|
let notifications: z.infer<typeof Notification>[] = [];
|
||||||
|
|
||||||
// Create some test notifications: follow, favourite, reblog, mention
|
// Create some test notifications: follow, favourite, reblog, mention
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
|
await using client0 = await generateClient(users[0]);
|
||||||
method: "POST",
|
await using client1 = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
notifications = await fakeRequest("/api/v1/notifications", {
|
const { ok } = await client1.followAccount(users[0].id);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
}).then((r) => r.json());
|
|
||||||
|
|
||||||
expect(notifications.length).toBe(1);
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
|
const { data } = await client0.getNotifications();
|
||||||
|
|
||||||
|
expect(data).toBeArrayOfSize(1);
|
||||||
|
notifications = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -31,55 +27,41 @@ afterAll(async () => {
|
||||||
|
|
||||||
describe("/api/v1/notifications/:id/dismiss", () => {
|
describe("/api/v1/notifications/:id/dismiss", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/notifications/${notifications[0].id}/dismiss`,
|
|
||||||
{
|
const { ok, raw } = await client.dismissNotification(
|
||||||
method: "POST",
|
notifications[0].id,
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should dismiss notification", async () => {
|
test("should dismiss notification", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/notifications/${notifications[0].id}/dismiss`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { ok } = await client.dismissNotification(notifications[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not display dismissed notification", async () => {
|
test("should not display dismissed notification", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getNotifications();
|
||||||
|
|
||||||
const output = await response.json();
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(0);
|
||||||
expect(output.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not be able to dismiss other user's notifications", async () => {
|
test("should not be able to dismiss other user's notifications", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
`/api/v1/notifications/${notifications[0].id}/dismiss`,
|
|
||||||
{
|
const { ok, raw } = await client.dismissNotification(
|
||||||
method: "POST",
|
notifications[0].id,
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,23 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Notification as ApiNotification } from "@versia/client/types";
|
import type { z } from "@hono/zod-openapi";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import type { Notification } from "@versia/client-ng/schemas";
|
||||||
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let notifications: ApiNotification[] = [];
|
let notifications: z.infer<typeof Notification>[] = [];
|
||||||
|
|
||||||
// Create some test notifications: follow, favourite, reblog, mention
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
|
await using client0 = await generateClient(users[0]);
|
||||||
method: "POST",
|
await using client1 = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
notifications = await fakeRequest("/api/v1/notifications", {
|
const { ok } = await client1.followAccount(users[0].id);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
}).then((r) => r.json());
|
|
||||||
|
|
||||||
expect(notifications.length).toBe(1);
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
|
const { data } = await client0.getNotifications();
|
||||||
|
|
||||||
|
expect(data).toBeArrayOfSize(1);
|
||||||
|
notifications = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -32,66 +27,54 @@ afterAll(async () => {
|
||||||
// /api/v1/notifications/:id
|
// /api/v1/notifications/:id
|
||||||
describe("/api/v1/notifications/:id", () => {
|
describe("/api/v1/notifications/:id", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
"/api/v1/notifications/00000000-0000-0000-0000-000000000000",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getNotification(notifications[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 if ID is invalid", async () => {
|
test("should return 422 if ID is invalid", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications/invalid", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
const { ok, raw } = await client.getNotification("invalid");
|
||||||
},
|
|
||||||
});
|
expect(ok).toBe(false);
|
||||||
expect(response.status).toBe(422);
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if notification not found", async () => {
|
test("should return 404 if notification not found", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/notifications/00000000-0000-0000-0000-000000000000",
|
|
||||||
{
|
const { ok, raw } = await client.getNotification(
|
||||||
headers: {
|
"00000000-0000-0000-0000-000000000000",
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return notification", async () => {
|
test("should return notification", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/notifications/${notifications[0].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { ok, data } = await client.getNotification(notifications[0].id);
|
||||||
|
|
||||||
const notification = (await response.json()) as ApiNotification;
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
expect(notification).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
expect(notification.id).toBe(notifications[0].id);
|
expect(data.id).toBe(notifications[0].id);
|
||||||
expect(notification.type).toBe("follow");
|
expect(data.type).toBe("follow");
|
||||||
expect(notification.account).toBeDefined();
|
expect(data.account).toBeDefined();
|
||||||
expect(notification.account?.id).toBe(users[1].id);
|
expect(data.account?.id).toBe(users[1].id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not be able to view other user's notifications", async () => {
|
test("should not be able to view other user's notifications", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
`/api/v1/notifications/${notifications[0].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
const { ok, raw } = await client.getNotification(notifications[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,21 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Notification as ApiNotification } from "@versia/client/types";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let notifications: ApiNotification[] = [];
|
|
||||||
|
|
||||||
// Create some test notifications: follow, favourite, reblog, mention
|
// Create some test notifications: follow, favourite, reblog, mention
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
|
await using client1 = await generateClient(users[1]);
|
||||||
method: "POST",
|
await using client0 = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
notifications = await fakeRequest("/api/v1/notifications", {
|
const { ok } = await client1.followAccount(users[0].id);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
}).then((r) => r.json());
|
|
||||||
|
|
||||||
expect(notifications.length).toBe(1);
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
|
const { data, ok: ok2 } = await client0.getNotifications();
|
||||||
|
|
||||||
|
expect(ok2).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -32,29 +25,23 @@ afterAll(async () => {
|
||||||
// /api/v1/notifications/clear
|
// /api/v1/notifications/clear
|
||||||
describe("/api/v1/notifications/clear", () => {
|
describe("/api/v1/notifications/clear", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications/clear", {
|
await using client = await generateClient();
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.dismissNotifications();
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should clear notifications", async () => {
|
test("should clear notifications", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications/clear", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { ok } = await client.dismissNotifications();
|
||||||
|
|
||||||
const newNotifications = await fakeRequest("/api/v1/notifications", {
|
expect(ok).toBe(true);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
}).then((r) => r.json());
|
|
||||||
|
|
||||||
expect(newNotifications.length).toBe(0);
|
const { data: newNotifications } = await client.getNotifications();
|
||||||
|
|
||||||
|
expect(newNotifications).toBeArrayOfSize(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,29 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Notification as ApiNotification } from "@versia/client/types";
|
import type { z } from "@hono/zod-openapi";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
import type { Notification } from "@versia/client-ng/schemas";
|
||||||
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
const statuses = await getTestStatuses(40, users[0]);
|
const statuses = await getTestStatuses(5, users[0]);
|
||||||
let notifications: ApiNotification[] = [];
|
let notifications: z.infer<typeof Notification>[] = [];
|
||||||
|
|
||||||
// Create some test notifications
|
// Create some test notifications
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
|
await using client0 = await generateClient(users[0]);
|
||||||
method: "POST",
|
await using client1 = await generateClient(users[1]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
const { ok } = await client1.followAccount(users[0].id);
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
expect(ok).toBe(true);
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const i of [0, 1, 2, 3]) {
|
for (const i of [0, 1, 2, 3]) {
|
||||||
await fakeRequest(`/api/v1/statuses/${statuses[i].id}/favourite`, {
|
await client1.favouriteStatus(statuses[i].id);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notifications = await fakeRequest("/api/v1/notifications", {
|
const { data } = await client0.getNotifications();
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
}).then((r) => r.json());
|
|
||||||
|
|
||||||
expect(notifications.length).toBe(5);
|
expect(data).toBeArrayOfSize(5);
|
||||||
|
notifications = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -44,45 +33,33 @@ afterAll(async () => {
|
||||||
// /api/v1/notifications/destroy_multiple
|
// /api/v1/notifications/destroy_multiple
|
||||||
describe("/api/v1/notifications/destroy_multiple", () => {
|
describe("/api/v1/notifications/destroy_multiple", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/notifications/destroy_multiple?${new URLSearchParams(
|
|
||||||
notifications.slice(1).map((n) => ["ids[]", n.id]),
|
const { ok, raw } = await client.dismissMultipleNotifications(
|
||||||
).toString()}`,
|
notifications.slice(1).map((n) => n.id),
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should dismiss notifications", async () => {
|
test("should dismiss notifications", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/notifications/destroy_multiple?${new URLSearchParams(
|
|
||||||
notifications.slice(1).map((n) => ["ids[]", n.id]),
|
const { ok, raw } = await client.dismissMultipleNotifications(
|
||||||
).toString()}`,
|
notifications.slice(1).map((n) => n.id),
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
expect(raw.status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not display dismissed notification", async () => {
|
test("should not display dismissed notifications", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data } = await client.getNotifications();
|
||||||
|
|
||||||
const output = await response.json();
|
expect(data).toBeArrayOfSize(1);
|
||||||
|
expect(data[0].id).toBe(notifications[0].id);
|
||||||
expect(output.length).toBe(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,33 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Notification as ApiNotification } from "@versia/client/types";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
|
||||||
|
|
||||||
const getFormData = (
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
object: Record<string, string | number | boolean>,
|
const timeline = (await getTestStatuses(5, users[0])).toReversed();
|
||||||
): FormData =>
|
|
||||||
Object.keys(object).reduce((formData, key) => {
|
|
||||||
formData.append(key, String(object[key]));
|
|
||||||
return formData;
|
|
||||||
}, new FormData());
|
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
|
||||||
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
|
||||||
// Create some test notifications: follow, favourite, reblog, mention
|
// Create some test notifications: follow, favourite, reblog, mention
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const res1 = await fakeRequest(`/api/v1/accounts/${users[0].id}/follow`, {
|
await using client = await generateClient(users[1]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res1.status).toBe(200);
|
const { ok } = await client.followAccount(users[0].id);
|
||||||
|
|
||||||
const res2 = await fakeRequest(
|
expect(ok).toBe(true);
|
||||||
`/api/v1/statuses/${timeline[0].id}/favourite`,
|
|
||||||
|
const { ok: ok2 } = await client.favouriteStatus(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok2).toBe(true);
|
||||||
|
|
||||||
|
const { ok: ok3 } = await client.reblogStatus(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok3).toBe(true);
|
||||||
|
|
||||||
|
const { ok: ok4 } = await client.postStatus(
|
||||||
|
`@${users[0].data.username} test mention`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(res2.status).toBe(200);
|
|
||||||
|
|
||||||
const res3 = await fakeRequest(
|
|
||||||
`/api/v1/statuses/${timeline[0].id}/reblog`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: getFormData({}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(res3.status).toBe(200);
|
|
||||||
|
|
||||||
const res4 = await fakeRequest("/api/v1/statuses", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: `@${users[0].data.username} test mention`,
|
|
||||||
visibility: "direct",
|
visibility: "direct",
|
||||||
local_only: "true",
|
local_only: true,
|
||||||
}),
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
expect(res4.status).toBe(200);
|
expect(ok4).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -74,27 +37,22 @@ afterAll(async () => {
|
||||||
// /api/v1/notifications
|
// /api/v1/notifications
|
||||||
describe("/api/v1/notifications", () => {
|
describe("/api/v1/notifications", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getNotifications();
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 200 with notifications", async () => {
|
test("should return 200 with notifications", async () => {
|
||||||
const response = await fakeRequest("/api/v1/notifications", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getNotifications();
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiNotification[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data.length).toBe(4);
|
||||||
expect(objects.length).toBe(4);
|
for (const [index, notification] of data.entries()) {
|
||||||
for (const [index, notification] of objects.entries()) {
|
|
||||||
expect(notification.account).toBeDefined();
|
expect(notification.account).toBeDefined();
|
||||||
expect(notification.account?.id).toBe(users[1].id);
|
expect(notification.account?.id).toBe(users[1].id);
|
||||||
expect(notification.created_at).toBeDefined();
|
expect(notification.created_at).toBeDefined();
|
||||||
|
|
@ -103,64 +61,43 @@ describe("/api/v1/notifications", () => {
|
||||||
expect(notification.type).toBe(
|
expect(notification.type).toBe(
|
||||||
["follow", "favourite", "reblog", "mention"].toReversed()[
|
["follow", "favourite", "reblog", "mention"].toReversed()[
|
||||||
index
|
index
|
||||||
],
|
] as "follow" | "favourite" | "reblog" | "mention",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not return notifications with filtered keywords", async () => {
|
test("should not return notifications with filtered keywords", async () => {
|
||||||
const filterResponse = await fakeRequest("/api/v2/filters", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data: filter, ok } = await client.createFilter(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
["notifications"],
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Test Filter",
|
||||||
|
"hide",
|
||||||
|
{
|
||||||
|
keywords_attributes: [
|
||||||
|
{
|
||||||
|
keyword: timeline[0].content.slice(4, 20),
|
||||||
|
whole_word: false,
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
],
|
||||||
title: "Test Filter",
|
|
||||||
"context[]": "notifications",
|
|
||||||
filter_action: "hide",
|
|
||||||
"keywords_attributes[0][keyword]": timeline[0].content.slice(
|
|
||||||
4,
|
|
||||||
20,
|
|
||||||
),
|
|
||||||
"keywords_attributes[0][whole_word]": "false",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(filterResponse.status).toBe(200);
|
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v1/notifications?limit=20", {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiNotification[];
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
expect(objects.length).toBe(3);
|
const { data: notifications } = await client.getNotifications();
|
||||||
|
|
||||||
|
expect(notifications.length).toBe(3);
|
||||||
// There should be no element with a status with id of timeline[0].id
|
// There should be no element with a status with id of timeline[0].id
|
||||||
expect(objects).not.toContainEqual(
|
expect(notifications).not.toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
status: expect.objectContaining({ id: timeline[0].id }),
|
status: expect.objectContaining({ id: timeline[0].id }),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Delete filter
|
// Delete filter
|
||||||
const filterDeleteResponse = await fakeRequest(
|
const { ok: ok2 } = await client.deleteFilter(filter.id);
|
||||||
`/api/v2/filters/${(await filterResponse.json()).id}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(filterDeleteResponse.status).toBe(204);
|
expect(ok2).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
||||||
import { PushSubscription } from "@versia/kit/db";
|
import { PushSubscription } from "@versia/kit/db";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
||||||
|
|
||||||
|
|
@ -15,42 +15,34 @@ beforeEach(async () => {
|
||||||
|
|
||||||
describe("/api/v1/push/subscriptions", () => {
|
describe("/api/v1/push/subscriptions", () => {
|
||||||
test("should create a push subscription", async () => {
|
test("should create a push subscription", async () => {
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, data } = await client.subscribePushNotifications(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
{
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
data: {
|
|
||||||
alerts: {
|
|
||||||
update: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
policy: "all",
|
|
||||||
subscription: {
|
|
||||||
endpoint: "https://example.com",
|
endpoint: "https://example.com",
|
||||||
keys: {
|
keys: {
|
||||||
|
auth: "test",
|
||||||
p256dh: "test",
|
p256dh: "test",
|
||||||
auth: "testthatis24charactersha",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
{
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
|
||||||
|
|
||||||
const body = await res.json();
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
endpoint: "https://example.com",
|
|
||||||
alerts: {
|
alerts: {
|
||||||
update: true,
|
update: true,
|
||||||
},
|
},
|
||||||
|
policy: "all",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data.endpoint).toBe("https://example.com");
|
||||||
|
expect(data.alerts).toMatchObject({
|
||||||
|
update: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should retrieve the same push subscription", async () => {
|
test("should retrieve the same push subscription", async () => {
|
||||||
|
await using client = await generateClient(users[1]);
|
||||||
|
|
||||||
await PushSubscription.insert({
|
await PushSubscription.insert({
|
||||||
endpoint: "https://example.com",
|
endpoint: "https://example.com",
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
@ -68,28 +60,21 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
policy: "all",
|
policy: "all",
|
||||||
authSecret: "test",
|
authSecret: "test",
|
||||||
publicKey: "test",
|
publicKey: "test",
|
||||||
tokenId: tokens[1].id,
|
tokenId: client.dbToken.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
const { ok, data } = await client.getPushSubscription();
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
expect(data.endpoint).toBe("https://example.com");
|
||||||
const body = await res.json();
|
expect(data.alerts).toMatchObject({
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
endpoint: "https://example.com",
|
|
||||||
alerts: {
|
|
||||||
update: true,
|
update: true,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should update a push subscription", async () => {
|
test("should update a push subscription", async () => {
|
||||||
|
await using client = await generateClient(users[0]);
|
||||||
|
|
||||||
await PushSubscription.insert({
|
await PushSubscription.insert({
|
||||||
endpoint: "https://example.com",
|
endpoint: "https://example.com",
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
@ -107,65 +92,49 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
policy: "all",
|
policy: "all",
|
||||||
authSecret: "test",
|
authSecret: "test",
|
||||||
publicKey: "test",
|
publicKey: "test",
|
||||||
tokenId: tokens[0].id,
|
tokenId: client.dbToken.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
const { ok, data } = await client.updatePushSubscription(
|
||||||
method: "PUT",
|
{
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
data: {
|
|
||||||
alerts: {
|
alerts: {
|
||||||
update: false,
|
update: false,
|
||||||
favourite: true,
|
favourite: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
policy: "follower",
|
"follower",
|
||||||
}),
|
);
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(await res.json()).toMatchObject({
|
expect(data.alerts).toMatchObject({
|
||||||
alerts: {
|
|
||||||
update: false,
|
update: false,
|
||||||
favourite: true,
|
favourite: true,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("permissions", () => {
|
describe("permissions", () => {
|
||||||
test("should not allow watching admin reports without permissions", async () => {
|
test("should not allow watching admin reports without permissions", async () => {
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.subscribePushNotifications(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
{
|
||||||
"Content-Type": "application/json",
|
endpoint: "https://example.com",
|
||||||
|
keys: {
|
||||||
|
auth: "testthatis24charactersha",
|
||||||
|
p256dh: "test",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
},
|
||||||
data: {
|
{
|
||||||
alerts: {
|
alerts: {
|
||||||
"admin.report": true,
|
"admin.report": true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
policy: "all",
|
policy: "all",
|
||||||
subscription: {
|
|
||||||
endpoint: "https://example.com",
|
|
||||||
keys: {
|
|
||||||
p256dh: "test",
|
|
||||||
auth: "testthatis24charactersha",
|
|
||||||
},
|
},
|
||||||
},
|
);
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(await res.json()).toMatchObject({
|
expect(data.alerts).toMatchObject({
|
||||||
alerts: {
|
|
||||||
"admin.report": false,
|
"admin.report": false,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -174,30 +143,28 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, data } = await client.subscribePushNotifications(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
{
|
||||||
"Content-Type": "application/json",
|
endpoint: "https://example.com",
|
||||||
|
keys: {
|
||||||
|
auth: "testthatis24charactersha",
|
||||||
|
p256dh: "test",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
},
|
||||||
data: {
|
{
|
||||||
alerts: {
|
alerts: {
|
||||||
"admin.report": true,
|
"admin.report": true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
policy: "all",
|
policy: "all",
|
||||||
subscription: {
|
|
||||||
endpoint: "https://example.com",
|
|
||||||
keys: {
|
|
||||||
p256dh: "test",
|
|
||||||
auth: "testthatis24charactersha",
|
|
||||||
},
|
},
|
||||||
},
|
);
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
expect(data.alerts).toMatchObject({
|
||||||
|
"admin.report": true,
|
||||||
|
});
|
||||||
|
|
||||||
await users[0].update({
|
await users[0].update({
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
|
@ -205,6 +172,8 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not allow editing to add admin reports without permissions", async () => {
|
test("should not allow editing to add admin reports without permissions", async () => {
|
||||||
|
await using client = await generateClient(users[0]);
|
||||||
|
|
||||||
await PushSubscription.insert({
|
await PushSubscription.insert({
|
||||||
endpoint: "https://example.com",
|
endpoint: "https://example.com",
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
@ -222,30 +191,21 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
policy: "all",
|
policy: "all",
|
||||||
authSecret: "test",
|
authSecret: "test",
|
||||||
publicKey: "test",
|
publicKey: "test",
|
||||||
tokenId: tokens[0].id,
|
tokenId: client.dbToken.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
const { ok, data } = await client.updatePushSubscription(
|
||||||
method: "PUT",
|
{
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
data: {
|
|
||||||
alerts: {
|
alerts: {
|
||||||
"admin.report": true,
|
"admin.report": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
policy: "all",
|
"all",
|
||||||
}),
|
);
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(await res.json()).toMatchObject({
|
expect(data.alerts).toMatchObject({
|
||||||
alerts: {
|
|
||||||
"admin.report": false,
|
"admin.report": false,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -254,6 +214,8 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await using client = await generateClient(users[0]);
|
||||||
|
|
||||||
await PushSubscription.insert({
|
await PushSubscription.insert({
|
||||||
endpoint: "https://example.com",
|
endpoint: "https://example.com",
|
||||||
alerts: {
|
alerts: {
|
||||||
|
|
@ -271,28 +233,20 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
policy: "all",
|
policy: "all",
|
||||||
authSecret: "test",
|
authSecret: "test",
|
||||||
publicKey: "test",
|
publicKey: "test",
|
||||||
tokenId: tokens[0].id,
|
tokenId: client.dbToken.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fakeRequest("/api/v1/push/subscription", {
|
const { ok, data } = await client.updatePushSubscription(
|
||||||
method: "PUT",
|
{
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
data: {
|
|
||||||
alerts: {
|
alerts: {
|
||||||
"admin.report": true,
|
"admin.report": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
policy: "all",
|
"all",
|
||||||
}),
|
);
|
||||||
});
|
|
||||||
|
|
||||||
expect(res.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(await res.json()).toMatchObject({
|
expect(data.alerts).toMatchObject({
|
||||||
alerts: {
|
|
||||||
update: true,
|
update: true,
|
||||||
"admin.report": true,
|
"admin.report": true,
|
||||||
"admin.sign_up": false,
|
"admin.sign_up": false,
|
||||||
|
|
@ -303,7 +257,6 @@ describe("/api/v1/push/subscriptions", () => {
|
||||||
poll: false,
|
poll: false,
|
||||||
reblog: false,
|
reblog: false,
|
||||||
status: false,
|
status: false,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await users[0].update({
|
await users[0].update({
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
let role: Role;
|
let role: Role;
|
||||||
let higherPriorityRole: Role;
|
let higherPriorityRole: Role;
|
||||||
|
|
||||||
|
|
@ -44,38 +44,32 @@ afterAll(async () => {
|
||||||
// /api/v1/roles/:id
|
// /api/v1/roles/:id
|
||||||
describe("/api/v1/roles/:id", () => {
|
describe("/api/v1/roles/:id", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
await using client = await generateClient();
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getRole(role.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if role does not exist", async () => {
|
test("should return 404 if role does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/roles/00000000-0000-0000-0000-000000000000",
|
|
||||||
{
|
const { ok, raw } = await client.getRole(
|
||||||
method: "GET",
|
"00000000-0000-0000-0000-000000000000",
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return role data", async () => {
|
test("should return role data", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { ok, data } = await client.getRole(role.id);
|
||||||
const responseData = await response.json();
|
|
||||||
expect(responseData).toMatchObject({
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toMatchObject({
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: role.data.name,
|
name: role.data.name,
|
||||||
permissions: role.data.permissions,
|
permissions: role.data.permissions,
|
||||||
|
|
@ -87,63 +81,56 @@ describe("/api/v1/roles/:id", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
await using client = await generateClient();
|
||||||
method: "PATCH",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
const { ok, raw } = await client.updateRole(role.id, {
|
||||||
body: JSON.stringify({ name: "updatedName" }),
|
name: "updatedName",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if role does not exist", async () => {
|
test("should return 404 if role does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/roles/00000000-0000-0000-0000-000000000000",
|
|
||||||
|
const { ok, raw } = await client.updateRole(
|
||||||
|
"00000000-0000-0000-0000-000000000000",
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
name: "updatedName",
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: "updatedName" }),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should update role data", async () => {
|
test("should update role data", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
const { ok } = await client.updateRole(role.id, {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
name: "updatedName",
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: "updatedName" }),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
const updatedRole = await Role.fromId(role.id);
|
const updatedRole = await Role.fromId(role.id);
|
||||||
expect(updatedRole?.data.name).toBe("updatedName");
|
expect(updatedRole?.data.name).toBe("updatedName");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 403 if user tries to update role with higher priority", async () => {
|
test("should return 403 if user tries to update role with higher priority", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/roles/${higherPriorityRole.id}`,
|
|
||||||
|
const { data, ok, raw } = await client.updateRole(
|
||||||
|
higherPriorityRole.id,
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
name: "updatedName",
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ name: "updatedName" }),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Forbidden",
|
error: "Forbidden",
|
||||||
details:
|
details:
|
||||||
"User with highest role priority 2 cannot edit role with priority 3",
|
"User with highest role priority 2 cannot edit role with priority 3",
|
||||||
|
|
@ -151,45 +138,38 @@ describe("/api/v1/roles/:id", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 403 if user tries to update role with permissions they do not have", async () => {
|
test("should return 403 if user tries to update role with permissions they do not have", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
const { data, ok, raw } = await client.updateRole(role.id, {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
permissions: [RolePermissions.Impersonate],
|
permissions: [RolePermissions.Impersonate],
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Forbidden",
|
error: "Forbidden",
|
||||||
details: "User cannot add or remove permissions they do not have",
|
details: "User cannot add or remove permissions they do not have",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(`/api/v1/roles/${role.id}`, {
|
await using client = await generateClient();
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.deleteRole(role.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 404 if role does not exist", async () => {
|
test("should return 404 if role does not exist", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/roles/00000000-0000-0000-0000-000000000000",
|
|
||||||
{
|
const { ok, raw } = await client.deleteRole(
|
||||||
method: "DELETE",
|
"00000000-0000-0000-0000-000000000000",
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should delete role", async () => {
|
test("should delete role", async () => {
|
||||||
|
|
@ -202,33 +182,26 @@ describe("/api/v1/roles/:id", () => {
|
||||||
icon: "test",
|
icon: "test",
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await fakeRequest(`/api/v1/roles/${newRole.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
const { ok } = await client.deleteRole(newRole.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
const deletedRole = await Role.fromId(newRole.id);
|
const deletedRole = await Role.fromId(newRole.id);
|
||||||
expect(deletedRole).toBeNull();
|
expect(deletedRole).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 403 if user tries to delete role with higher priority", async () => {
|
test("should return 403 if user tries to delete role with higher priority", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/roles/${higherPriorityRole.id}`,
|
|
||||||
{
|
const { data, ok, raw } = await client.deleteRole(
|
||||||
method: "DELETE",
|
higherPriorityRole.id,
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Forbidden",
|
error: "Forbidden",
|
||||||
details:
|
details:
|
||||||
"User with highest role priority 2 cannot delete role with priority 3",
|
"User with highest role priority 2 cannot delete role with priority 3",
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import { Role } from "@versia/kit/db";
|
import { Role } from "@versia/kit/db";
|
||||||
import { RolePermissions } from "@versia/kit/tables";
|
import { RolePermissions } from "@versia/kit/tables";
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, deleteUsers, tokens } = await getTestUsers(1);
|
const { users, deleteUsers } = await getTestUsers(1);
|
||||||
let role: Role;
|
let role: Role;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|
@ -32,24 +32,21 @@ afterAll(async () => {
|
||||||
// /api/v1/roles
|
// /api/v1/roles
|
||||||
describe("/api/v1/roles", () => {
|
describe("/api/v1/roles", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/roles", {
|
await using client = await generateClient();
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getRoles();
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return a list of roles", async () => {
|
test("should return a list of roles", async () => {
|
||||||
const response = await fakeRequest("/api/v1/roles", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
const { ok, data } = await client.getRoles();
|
||||||
const roles = await response.json();
|
|
||||||
expect(roles).toContainEqual({
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toContainEqual({
|
||||||
name: "test",
|
name: "test",
|
||||||
permissions: [RolePermissions.ManageRoles],
|
permissions: [RolePermissions.ManageRoles],
|
||||||
priority: 10,
|
priority: 10,
|
||||||
|
|
@ -59,7 +56,7 @@ describe("/api/v1/roles", () => {
|
||||||
id: role.id,
|
id: role.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(roles).toContainEqual({
|
expect(data).toContainEqual({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default",
|
name: "Default",
|
||||||
permissions: config.permissions.default,
|
permissions: config.permissions.default,
|
||||||
|
|
@ -70,25 +67,18 @@ describe("/api/v1/roles", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create a new role", async () => {
|
test("should create a new role", async () => {
|
||||||
const response = await fakeRequest("/api/v1/roles", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, data } = await client.createRole("newRole", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: "newRole",
|
|
||||||
permissions: [RolePermissions.ManageRoles],
|
permissions: [RolePermissions.ManageRoles],
|
||||||
priority: 1,
|
priority: 1,
|
||||||
description: "newRole",
|
description: "newRole",
|
||||||
visible: true,
|
visible: true,
|
||||||
icon: "https://example.com/icon.png",
|
icon: "https://example.com/icon.png",
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.ok).toBe(true);
|
expect(ok).toBe(true);
|
||||||
const newRole = await response.json();
|
expect(data).toMatchObject({
|
||||||
expect(newRole).toMatchObject({
|
|
||||||
name: "newRole",
|
name: "newRole",
|
||||||
permissions: [RolePermissions.ManageRoles],
|
permissions: [RolePermissions.ManageRoles],
|
||||||
priority: 1,
|
priority: 1,
|
||||||
|
|
@ -98,7 +88,7 @@ describe("/api/v1/roles", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
const createdRole = await Role.fromId(newRole.id);
|
const createdRole = await Role.fromId(data.id);
|
||||||
|
|
||||||
expect(createdRole).toBeDefined();
|
expect(createdRole).toBeDefined();
|
||||||
|
|
||||||
|
|
@ -106,49 +96,37 @@ describe("/api/v1/roles", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 403 if user tries to create a role with higher priority", async () => {
|
test("should return 403 if user tries to create a role with higher priority", async () => {
|
||||||
const response = await fakeRequest("/api/v1/roles", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok, raw } = await client.createRole("newRole", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: "newRole",
|
|
||||||
permissions: [RolePermissions.ManageBlocks],
|
permissions: [RolePermissions.ManageBlocks],
|
||||||
priority: 11,
|
priority: 11,
|
||||||
description: "newRole",
|
description: "newRole",
|
||||||
visible: true,
|
visible: true,
|
||||||
icon: "https://example.com/icon.png",
|
icon: "https://example.com/icon.png",
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Cannot create role with higher priority than your own",
|
error: "Cannot create role with higher priority than your own",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 403 if user tries to create a role with permissions they do not have", async () => {
|
test("should return 403 if user tries to create a role with permissions they do not have", async () => {
|
||||||
const response = await fakeRequest("/api/v1/roles", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok, raw } = await client.createRole("newRole", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: "newRole",
|
|
||||||
permissions: [RolePermissions.Impersonate],
|
permissions: [RolePermissions.Impersonate],
|
||||||
priority: 1,
|
priority: 1,
|
||||||
description: "newRole",
|
description: "newRole",
|
||||||
visible: true,
|
visible: true,
|
||||||
icon: "https://example.com/icon.png",
|
icon: "https://example.com/icon.png",
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(403);
|
expect(ok).toBe(false);
|
||||||
const output = await response.json();
|
expect(raw.status).toBe(403);
|
||||||
expect(output).toMatchObject({
|
expect(data).toMatchObject({
|
||||||
error: "Cannot create role with permissions you do not have",
|
error: "Cannot create role with permissions you do not have",
|
||||||
details: "Forbidden permissions: impersonate",
|
details: "Forbidden permissions: impersonate",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,43 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import type { Status as ApiStatus } from "@versia/client/types";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
const timeline = (await getTestStatuses(2, users[0])).toReversed();
|
const timeline = (await getTestStatuses(2, users[0])).toReversed();
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v1/statuses/:id/favourite
|
|
||||||
describe("/api/v1/statuses/:id/favourite", () => {
|
describe("/api/v1/statuses/:id/favourite", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/statuses/${timeline[0].id}/favourite`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.favouriteStatus(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should favourite post", async () => {
|
test("should favourite post", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
`/api/v1/statuses/${timeline[0].id}/favourite`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
|
|
||||||
const json = (await response.json()) as ApiStatus;
|
const { data, ok } = await client.favouriteStatus(timeline[0].id);
|
||||||
|
|
||||||
expect(json.favourited).toBe(true);
|
expect(ok).toBe(true);
|
||||||
expect(json.favourites_count).toBe(1);
|
expect(data).toMatchObject({
|
||||||
|
favourited: true,
|
||||||
|
favourites_count: 1,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("post should be favourited when fetched", async () => {
|
test("post should be favourited when fetched", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
`/api/v1/statuses/${timeline[0].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data } = await client.getStatus(timeline[0].id);
|
||||||
|
|
||||||
const json = (await response.json()) as ApiStatus;
|
expect(data).toMatchObject({
|
||||||
|
favourited: true,
|
||||||
expect(json.favourited).toBe(true);
|
favourites_count: 1,
|
||||||
expect(json.favourites_count).toBe(1);
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,42 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Account as ApiAccount } from "@versia/client/types";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
const timeline = (await getTestStatuses(5, users[0])).toReversed();
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
await using client = await generateClient(users[1]);
|
||||||
|
|
||||||
for (const status of timeline) {
|
for (const status of timeline) {
|
||||||
await fakeRequest(`/api/v1/statuses/${status.id}/favourite`, {
|
await client.favouriteStatus(status.id);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v1/statuses/:id/favourited_by
|
// /api/v1/statuses/:id/favourited_by
|
||||||
describe("/api/v1/statuses/:id/favourited_by", () => {
|
describe("/api/v1/statuses/:id/favourited_by", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/statuses/${timeline[0].id}/favourited_by`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getStatusFavouritedBy(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 200 with users", async () => {
|
test("should return 200 with users", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/statuses/${timeline[0].id}/favourited_by`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getStatusFavouritedBy(timeline[0].id);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiAccount[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(1);
|
||||||
|
|
||||||
expect(objects.length).toBe(1);
|
expect(data.length).toBe(1);
|
||||||
for (const [, status] of objects.entries()) {
|
expect(data[0].id).toBe(users[1].id);
|
||||||
expect(status.id).toBe(users[1].id);
|
expect(data[0].username).toBe(users[1].data.username);
|
||||||
expect(status.username).toBe(users[1].data.username);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,41 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Account as ApiAccount } from "@versia/client/types";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
const timeline = (await getTestStatuses(5, users[0])).toReversed();
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
await using client = await generateClient(users[1]);
|
||||||
|
|
||||||
for (const status of timeline) {
|
for (const status of timeline) {
|
||||||
await fakeRequest(`/api/v1/statuses/${status.id}/reblog`, {
|
await client.reblogStatus(status.id);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v1/statuses/:id/reblogged_by
|
|
||||||
describe("/api/v1/statuses/:id/reblogged_by", () => {
|
describe("/api/v1/statuses/:id/reblogged_by", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/statuses/${timeline[0].id}/reblogged_by`,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getStatusRebloggedBy(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 200 with users", async () => {
|
test("should return 200 with users", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/statuses/${timeline[0].id}/reblogged_by`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getStatusRebloggedBy(timeline[0].id);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiAccount[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(1);
|
||||||
|
|
||||||
expect(objects.length).toBe(1);
|
expect(data.length).toBe(1);
|
||||||
for (const [, status] of objects.entries()) {
|
expect(data[0].id).toBe(users[1].id);
|
||||||
expect(status.id).toBe(users[1].id);
|
expect(data[0].username).toBe(users[1].data.username);
|
||||||
expect(status.username).toBe(users[1].data.username);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,58 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import type { Status as ApiStatus } from "@versia/client/types";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
const timeline = (await getTestStatuses(2, users[0])).toReversed();
|
const timeline = (await getTestStatuses(2, users[0])).toReversed();
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v1/statuses/:id/unfavourite
|
|
||||||
describe("/api/v1/statuses/:id/unfavourite", () => {
|
describe("/api/v1/statuses/:id/unfavourite", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient();
|
||||||
`/api/v1/statuses/${timeline[0].id}/unfavourite`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.unfavouriteStatus(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should be able to unfavourite post that is not favourited", async () => {
|
test("should be able to unfavourite post that is not favourited", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
`/api/v1/statuses/${timeline[0].id}/unfavourite`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { ok } = await client.unfavouriteStatus(timeline[0].id);
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should unfavourite post", async () => {
|
test("should unfavourite post", async () => {
|
||||||
beforeAll(async () => {
|
await using client = await generateClient(users[1]);
|
||||||
await fakeRequest(`/api/v1/statuses/${timeline[1].id}/favourite`, {
|
|
||||||
method: "POST",
|
await client.favouriteStatus(timeline[1].id);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
const { ok } = await client.unfavouriteStatus(timeline[1].id);
|
||||||
},
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
|
const { ok: ok2, data } = await client.getStatus(timeline[1].id);
|
||||||
|
|
||||||
|
expect(ok2).toBe(true);
|
||||||
|
expect(data).toMatchObject({
|
||||||
|
favourited: false,
|
||||||
|
favourites_count: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await fakeRequest(
|
|
||||||
`/api/v1/statuses/${timeline[1].id}/unfavourite`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
|
|
||||||
const json = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
expect(json.favourited).toBe(false);
|
|
||||||
expect(json.favourites_count).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("post should not be favourited when fetched", async () => {
|
test("post should not be favourited when fetched", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[1]);
|
||||||
`/api/v1/statuses/${timeline[1].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[1].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { ok, data } = await client.getStatus(timeline[1].id);
|
||||||
|
|
||||||
const json = (await response.json()) as ApiStatus;
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toMatchObject({
|
||||||
expect(json.favourited).toBe(false);
|
favourited: false,
|
||||||
expect(json.favourites_count).toBe(0);
|
favourites_count: 0,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||||
import type { Status as ApiStatus } from "@versia/client/types";
|
import type { z } from "@hono/zod-openapi";
|
||||||
|
import type { Status } from "@versia/client-ng/schemas";
|
||||||
import { Media, db } from "@versia/kit/db";
|
import { Media, db } from "@versia/kit/db";
|
||||||
import { Emojis } from "@versia/kit/tables";
|
import { Emojis } from "@versia/kit/tables";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
let media: Media;
|
let media: Media;
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -32,266 +33,172 @@ beforeAll(async () => {
|
||||||
|
|
||||||
describe("/api/v1/statuses", () => {
|
describe("/api/v1/statuses", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient();
|
||||||
method: "POST",
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.postStatus("Hello, world!");
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 is status is empty", async () => {
|
test("should return 422 is status is empty", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams(),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
const { ok, raw } = await client.postStatus("");
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 is status is too long", async () => {
|
test("should return 422 is status is too long", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "a".repeat(config.validation.notes.max_characters + 1),
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
const { ok, raw } = await client.postStatus(
|
||||||
|
"a".repeat(config.validation.notes.max_characters + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 is visibility is invalid", async () => {
|
test("should return 422 is visibility is invalid", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, raw } = await client.postStatus("Hello, world!", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
visibility: "invalid" as z.infer<typeof Status>["visibility"],
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
visibility: "invalid",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 if scheduled_at is invalid", async () => {
|
test("should return 422 if scheduled_at is invalid", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok, raw } = await client.postStatus("Hello, world!", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
scheduled_at: new Date(Date.now() - 1000),
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
scheduled_at: "invalid",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
|
expect(data).toMatchObject({
|
||||||
|
error: expect.stringContaining(
|
||||||
|
"must be at least 5 minutes in the future",
|
||||||
|
),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 is in_reply_to_id is invalid", async () => {
|
test("should return 422 is in_reply_to_id is invalid", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, raw } = await client.postStatus("Hello, world!", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
in_reply_to_id: "invalid",
|
in_reply_to_id: "invalid",
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 is quote_id is invalid", async () => {
|
test("should return 422 is quote_id is invalid", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, raw } = await client.postStatus("Hello, world!", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
quote_id: "invalid",
|
quote_id: "invalid",
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 422 is media_ids is invalid", async () => {
|
test("should return 422 is media_ids is invalid", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { ok, raw } = await client.postStatus("Hello, world!", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
media_ids: ["invalid"],
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
"media_ids[]": "invalid",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(422);
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(422);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create a post", async () => {
|
test("should create a post", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.postStatus("Hello, world!");
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
expect(ok).toBe(true);
|
||||||
body: new URLSearchParams({
|
expect(data).toMatchObject({
|
||||||
status: "Hello, world!",
|
content: "<p>Hello, world!</p>",
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
expect(object.content).toBe("<p>Hello, world!</p>");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create a post with visibility", async () => {
|
test("should create a post with visibility", async () => {
|
||||||
// This one uses JSON to test the interop
|
await using client = await generateClient(users[0]);
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
|
||||||
method: "POST",
|
const { data, ok } = await client.postStatus("Hello, world!", {
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
status: "Hello, world!",
|
|
||||||
visibility: "unlisted",
|
visibility: "unlisted",
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
expect(data).toMatchObject({
|
||||||
"application/json",
|
visibility: "unlisted",
|
||||||
);
|
content: "<p>Hello, world!</p>",
|
||||||
|
});
|
||||||
const object = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
expect(object.content).toBe("<p>Hello, world!</p>");
|
|
||||||
expect(object.visibility).toBe("unlisted");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create a post with a reply", async () => {
|
test("should create a post with a reply", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.postStatus("Hello, world!");
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
|
const { data: data2, ok: ok2 } = await client.postStatus(
|
||||||
|
"Hello, world again!",
|
||||||
|
{
|
||||||
|
in_reply_to_id: data.id,
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
const response2 = await fakeRequest("/api/v1/statuses", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world again!",
|
|
||||||
in_reply_to_id: object.id,
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response2.status).toBe(200);
|
|
||||||
expect(response2.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const object2 = (await response2.json()) as ApiStatus;
|
expect(ok2).toBe(true);
|
||||||
|
expect(data2).toMatchObject({
|
||||||
expect(object2.content).toBe("<p>Hello, world again!</p>");
|
content: "<p>Hello, world again!</p>",
|
||||||
expect(object2.in_reply_to_id).toBe(object.id);
|
in_reply_to_id: data.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create a post with a quote", async () => {
|
test("should create a post with a quote", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.postStatus("Hello, world!");
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
|
const { data: data2, ok: ok2 } = await client.postStatus(
|
||||||
|
"Hello, world again!",
|
||||||
|
{
|
||||||
|
quote_id: data.id,
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
const response2 = await fakeRequest("/api/v1/statuses", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world again!",
|
|
||||||
quote_id: object.id,
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response2.status).toBe(200);
|
|
||||||
expect(response2.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const object2 = (await response2.json()) as ApiStatus;
|
expect(ok2).toBe(true);
|
||||||
|
expect(data2).toMatchObject({
|
||||||
expect(object2.content).toBe("<p>Hello, world again!</p>");
|
content: "<p>Hello, world again!</p>",
|
||||||
expect(object2.quote?.id).toBe(object.id);
|
quote: expect.objectContaining({
|
||||||
|
id: data.id,
|
||||||
|
}),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correctly parse emojis", async () => {
|
test("should correctly parse emojis", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, :test:!",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.postStatus("Hello, :test:!");
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
expect(object.emojis).toBeArrayOfSize(1);
|
expect((data as z.infer<typeof Status>).emojis).toBeArrayOfSize(1);
|
||||||
expect(object.emojis[0]).toMatchObject({
|
expect((data as z.infer<typeof Status>).emojis[0]).toMatchObject({
|
||||||
shortcode: "test",
|
shortcode: "test",
|
||||||
url: expect.stringContaining("/media/proxy/"),
|
url: expect.stringContaining("/media/proxy/"),
|
||||||
});
|
});
|
||||||
|
|
@ -299,29 +206,20 @@ describe("/api/v1/statuses", () => {
|
||||||
|
|
||||||
describe("mentions testing", () => {
|
describe("mentions testing", () => {
|
||||||
test("should correctly parse @mentions", async () => {
|
test("should correctly parse @mentions", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.postStatus(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
`Hello, @${users[1].data.username}!`,
|
||||||
},
|
);
|
||||||
body: new URLSearchParams({
|
|
||||||
status: `Hello, @${users[1].data.username}!`,
|
expect(ok).toBe(true);
|
||||||
local_only: "true",
|
expect(data).toMatchObject({
|
||||||
}),
|
content: `<p>Hello, <a class="u-url mention" rel="nofollow noopener noreferrer" target="_blank" href="${users[1].getUri()}">@${users[1].data.username}</a>!</p>`,
|
||||||
});
|
});
|
||||||
|
expect((data as z.infer<typeof Status>).mentions).toBeArrayOfSize(
|
||||||
expect(response.status).toBe(200);
|
1,
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
expect((data as z.infer<typeof Status>).mentions[0]).toMatchObject({
|
||||||
const object = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
expect(object.content).toBe(
|
|
||||||
`<p>Hello, <a class="u-url mention" rel="nofollow noopener noreferrer" target="_blank" href="${users[1].getUri()}">@${users[1].data.username}</a>!</p>`,
|
|
||||||
);
|
|
||||||
expect(object.mentions).toBeArrayOfSize(1);
|
|
||||||
expect(object.mentions[0]).toMatchObject({
|
|
||||||
id: users[1].id,
|
id: users[1].id,
|
||||||
username: users[1].data.username,
|
username: users[1].data.username,
|
||||||
acct: users[1].data.username,
|
acct: users[1].data.username,
|
||||||
|
|
@ -329,28 +227,22 @@ describe("/api/v1/statuses", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correctly parse @mentions@domain", async () => {
|
test("should correctly parse @mentions@domain", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.postStatus(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
`Hello, @${users[1].data.username}@${
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: `Hello, @${users[1].data.username}@${
|
|
||||||
config.http.base_url.host
|
config.http.base_url.host
|
||||||
}!`,
|
}!`,
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toMatchObject({
|
||||||
expect(object.mentions).toBeArrayOfSize(1);
|
content: `<p>Hello, <a class="u-url mention" rel="nofollow noopener noreferrer" target="_blank" href="${users[1].getUri()}">@${users[1].data.username}</a>!</p>`,
|
||||||
expect(object.mentions[0]).toMatchObject({
|
});
|
||||||
|
expect((data as z.infer<typeof Status>).mentions).toBeArrayOfSize(
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
expect((data as z.infer<typeof Status>).mentions[0]).toMatchObject({
|
||||||
id: users[1].id,
|
id: users[1].id,
|
||||||
username: users[1].data.username,
|
username: users[1].data.username,
|
||||||
acct: users[1].data.username,
|
acct: users[1].data.username,
|
||||||
|
|
@ -360,83 +252,51 @@ describe("/api/v1/statuses", () => {
|
||||||
|
|
||||||
describe("HTML injection testing", () => {
|
describe("HTML injection testing", () => {
|
||||||
test("should not allow HTML injection", async () => {
|
test("should not allow HTML injection", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hi! <script>alert('Hello, world!');</script>",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.postStatus(
|
||||||
expect(response.headers.get("content-type")).toContain(
|
"Hi! <script>alert('Hello, world!');</script>",
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toMatchObject({
|
||||||
expect(object.content).toBe(
|
content:
|
||||||
"<p>Hi! <script>alert('Hello, world!');</script></p>",
|
"<p>Hi! <script>alert('Hello, world!');</script></p>",
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not allow HTML injection in spoiler_text", async () => {
|
test("should not allow HTML injection in spoiler_text", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.postStatus("Hello, world!", {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
spoiler_text: "uwu <script>alert('Hello, world!');</script>",
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "Hello, world!",
|
|
||||||
spoiler_text:
|
|
||||||
"uwu <script>alert('Hello, world!');</script>",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
expect(data).toMatchObject({
|
||||||
"application/json",
|
spoiler_text:
|
||||||
);
|
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
|
||||||
|
|
||||||
expect(object.spoiler_text).toBe(
|
|
||||||
"uwu <script>alert('Hello, world!');</script>",
|
"uwu <script>alert('Hello, world!');</script>",
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should rewrite all image and video src to go through proxy", async () => {
|
test("should rewrite all image and video src to go through proxy", async () => {
|
||||||
const response = await fakeRequest("/api/v1/statuses", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
status: "<img src='https://example.com/image.jpg'> <video src='https://example.com/video.mp4'> Test!",
|
|
||||||
local_only: "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.postStatus(
|
||||||
expect(response.headers.get("content-type")).toContain(
|
"<img src='https://example.com/image.jpg'> <video src='https://example.com/video.mp4'> Test!",
|
||||||
"application/json",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const object = (await response.json()) as ApiStatus;
|
expect(ok).toBe(true);
|
||||||
// Proxy url is base_url/media/proxy/<base64url encoded url>
|
// Proxy url is base_url/media/proxy/<base64url encoded url>
|
||||||
expect(object.content).toBe(
|
expect(data).toMatchObject({
|
||||||
`<p><img src="${config.http.base_url}media/proxy/${Buffer.from(
|
content: `<p><img src="${config.http.base_url}media/proxy/${Buffer.from(
|
||||||
"https://example.com/image.jpg",
|
"https://example.com/image.jpg",
|
||||||
).toString("base64url")}"> <video src="${
|
).toString("base64url")}"> <video src="${
|
||||||
config.http.base_url
|
config.http.base_url
|
||||||
}media/proxy/${Buffer.from(
|
}media/proxy/${Buffer.from(
|
||||||
"https://example.com/video.mp4",
|
"https://example.com/video.mp4",
|
||||||
).toString("base64url")}"> Test!</p>`,
|
).toString("base64url")}"> Test!</p>`,
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import type { Status as ApiStatus } from "@versia/client/types";
|
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
const timeline = (await getTestStatuses(10, users[0])).toReversed();
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
|
|
@ -12,44 +11,35 @@ afterAll(async () => {
|
||||||
|
|
||||||
describe("/api/v1/timelines/home", () => {
|
describe("/api/v1/timelines/home", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v1/timelines/home");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getHomeTimeline();
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correctly parse limit", async () => {
|
test("should correctly parse limit", async () => {
|
||||||
const response = await fakeRequest("/api/v1/timelines/home?limit=5", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
const { data, ok } = await client.getHomeTimeline({
|
||||||
},
|
limit: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
expect(data).toBeArrayOfSize(2);
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
|
||||||
|
|
||||||
expect(objects.length).toBe(5);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 200 with statuses", async () => {
|
test("should return 200 with statuses", async () => {
|
||||||
const response = await fakeRequest("/api/v1/timelines/home", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
const { data, ok } = await client.getHomeTimeline({
|
||||||
},
|
limit: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
expect(data).toBeArrayOfSize(5);
|
||||||
"application/json",
|
for (const [index, status] of data.entries()) {
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
|
||||||
|
|
||||||
expect(objects.length).toBe(20);
|
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
|
|
@ -60,81 +50,64 @@ describe("/api/v1/timelines/home", () => {
|
||||||
|
|
||||||
describe("should paginate properly", () => {
|
describe("should paginate properly", () => {
|
||||||
test("should send correct Link header", async () => {
|
test("should send correct Link header", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/timelines/home?limit=20",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.headers.get("link")).toBe(
|
const { data, ok, raw } = await client.getHomeTimeline({
|
||||||
`<${config.http.base_url}api/v1/timelines/home?limit=20&max_id=${timeline[19].id}>; rel="next"`,
|
limit: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
|
expect(raw.headers.get("link")).toBe(
|
||||||
|
`<${config.http.base_url}api/v1/timelines/home?limit=5&max_id=${timeline[4].id}>; rel="next"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correct statuses with max", async () => {
|
test("should correct statuses with max", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/timelines/home?limit=20&max_id=${timeline[19].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getHomeTimeline({
|
||||||
expect(response.headers.get("content-type")).toContain(
|
limit: 5,
|
||||||
"application/json",
|
max_id: timeline[4].id,
|
||||||
);
|
});
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
expect(objects.length).toBe(20);
|
for (const [index, status] of data.entries()) {
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
expect(status.created_at).toBeDefined();
|
expect(status.created_at).toBeDefined();
|
||||||
expect(status.id).toBe(timeline[index + 20].id);
|
expect(status.id).toBe(timeline[index + 5].id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should send correct Link prev header", async () => {
|
test("should send correct Link prev header", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/timelines/home?limit=20&max_id=${timeline[19].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.headers.get("link")).toInclude(
|
const { data, ok, raw } = await client.getHomeTimeline({
|
||||||
`${config.http.base_url}api/v1/timelines/home?limit=20&min_id=${timeline[20].id}>; rel="prev"`,
|
limit: 5,
|
||||||
|
max_id: timeline[4].id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
|
expect(raw.headers.get("link")).toInclude(
|
||||||
|
`<${config.http.base_url}api/v1/timelines/home?limit=5&min_id=${timeline[5].id}>; rel="prev"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correct statuses with min_id", async () => {
|
test("should correct statuses with min_id", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/timelines/home?limit=20&min_id=${timeline[20].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getHomeTimeline({
|
||||||
expect(response.headers.get("content-type")).toContain(
|
limit: 5,
|
||||||
"application/json",
|
min_id: timeline[5].id,
|
||||||
);
|
});
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
expect(objects.length).toBe(20);
|
for (const [index, status] of data.entries()) {
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
|
|
@ -144,58 +117,39 @@ describe("/api/v1/timelines/home", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not return statuses with filtered keywords", async () => {
|
test("should not return statuses with filtered keywords", async () => {
|
||||||
const filterResponse = await fakeRequest("/api/v2/filters", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.createFilter(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
["home"],
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Test Filter",
|
||||||
|
"hide",
|
||||||
|
{
|
||||||
|
keywords_attributes: [
|
||||||
|
{
|
||||||
|
keyword: timeline[0].content.slice(4, 20),
|
||||||
|
whole_word: false,
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
],
|
||||||
title: "Test Filter",
|
},
|
||||||
"context[]": "home",
|
);
|
||||||
filter_action: "hide",
|
|
||||||
"keywords_attributes[0][keyword]":
|
expect(ok).toBe(true);
|
||||||
timeline[0].content.slice(4, 20),
|
|
||||||
"keywords_attributes[0][whole_word]": "false",
|
const { data: data2, ok: ok2 } = await client.getHomeTimeline({
|
||||||
}),
|
limit: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(filterResponse.status).toBe(200);
|
expect(ok2).toBe(true);
|
||||||
|
expect(data2).toBeArrayOfSize(5);
|
||||||
const response = await fakeRequest(
|
|
||||||
"/api/v1/timelines/home?limit=20",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
|
||||||
|
|
||||||
expect(objects.length).toBe(20);
|
|
||||||
// There should be no element with id of timeline[0].id
|
// There should be no element with id of timeline[0].id
|
||||||
expect(objects).not.toContainEqual(
|
expect(data2).not.toContainEqual(
|
||||||
expect.objectContaining({ id: timeline[0].id }),
|
expect.objectContaining({ id: timeline[0].id }),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Delete filter
|
// Delete filter
|
||||||
const filterDeleteResponse = await fakeRequest(
|
const { ok: ok3 } = await client.deleteFilter(data.id);
|
||||||
`/api/v2/filters/${(await filterResponse.json()).id}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(filterDeleteResponse.status).toBe(204);
|
expect(ok3).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import type { Status as ApiStatus } from "@versia/client/types";
|
|
||||||
import { config } from "~/config.ts";
|
import { config } from "~/config.ts";
|
||||||
import { fakeRequest, getTestStatuses, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestStatuses, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
const { users, deleteUsers } = await getTestUsers(5);
|
||||||
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
const timeline = (await getTestStatuses(10, users[0])).toReversed();
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
|
|
@ -12,38 +11,26 @@ afterAll(async () => {
|
||||||
|
|
||||||
describe("/api/v1/timelines/public", () => {
|
describe("/api/v1/timelines/public", () => {
|
||||||
test("should correctly parse limit", async () => {
|
test("should correctly parse limit", async () => {
|
||||||
const response = await fakeRequest("/api/v1/timelines/public?limit=5", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
const { data, ok } = await client.getPublicTimeline({
|
||||||
},
|
limit: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
expect(data).toBeArrayOfSize(5);
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
|
||||||
|
|
||||||
expect(objects.length).toBe(5);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return 200 with statuses", async () => {
|
test("should return 200 with statuses", async () => {
|
||||||
const response = await fakeRequest("/api/v1/timelines/public", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
const { data, ok } = await client.getPublicTimeline({
|
||||||
},
|
limit: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
expect(response.headers.get("content-type")).toContain(
|
expect(data).toBeArrayOfSize(5);
|
||||||
"application/json",
|
for (const [index, status] of data.entries()) {
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
|
||||||
|
|
||||||
expect(objects.length).toBe(20);
|
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
|
|
@ -53,24 +40,16 @@ describe("/api/v1/timelines/public", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should only fetch local statuses", async () => {
|
test("should only fetch local statuses", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/timelines/public?limit=20&local=true",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getPublicTimeline({
|
||||||
expect(response.headers.get("content-type")).toContain(
|
limit: 5,
|
||||||
"application/json",
|
local: true,
|
||||||
);
|
});
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
expect(objects.length).toBe(20);
|
for (const [index, status] of data.entries()) {
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
|
|
@ -80,102 +59,77 @@ describe("/api/v1/timelines/public", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should only fetch remote statuses", async () => {
|
test("should only fetch remote statuses", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/timelines/public?remote=true&allow_local_only=false&only_media=false",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getPublicTimeline({
|
||||||
expect(response.headers.get("content-type")).toContain(
|
limit: 5,
|
||||||
"application/json",
|
remote: true,
|
||||||
);
|
});
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(0);
|
||||||
expect(objects).toBeArray();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("should paginate properly", () => {
|
describe("should paginate properly", () => {
|
||||||
test("should send correct Link header", async () => {
|
test("should send correct Link header", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
"/api/v1/timelines/public?limit=20",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.headers.get("link")).toBe(
|
const { data, ok, raw } = await client.getPublicTimeline({
|
||||||
`<${config.http.base_url}api/v1/timelines/public?limit=20&max_id=${timeline[19].id}>; rel="next"`,
|
limit: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
|
expect(raw.headers.get("link")).toBe(
|
||||||
|
`<${config.http.base_url}api/v1/timelines/public?limit=5&max_id=${timeline[4].id}>; rel="next"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correct statuses with max", async () => {
|
test("should correct statuses with max", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/timelines/public?limit=20&max_id=${timeline[19].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getPublicTimeline({
|
||||||
expect(response.headers.get("content-type")).toContain(
|
limit: 5,
|
||||||
"application/json",
|
max_id: timeline[4].id,
|
||||||
);
|
});
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
expect(objects.length).toBe(20);
|
for (const [index, status] of data.entries()) {
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
expect(status.created_at).toBeDefined();
|
expect(status.created_at).toBeDefined();
|
||||||
expect(status.id).toBe(timeline[index + 20].id);
|
expect(status.id).toBe(timeline[index + 5].id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should send correct Link prev header", async () => {
|
test("should send correct Link prev header", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/timelines/public?limit=20&max_id=${timeline[19].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.headers.get("link")).toInclude(
|
const { data, ok, raw } = await client.getPublicTimeline({
|
||||||
`${config.http.base_url}api/v1/timelines/public?limit=20&min_id=${timeline[20].id}>; rel="prev"`,
|
limit: 5,
|
||||||
|
max_id: timeline[4].id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
|
expect(raw.headers.get("link")).toInclude(
|
||||||
|
`<${config.http.base_url}api/v1/timelines/public?limit=5&min_id=${timeline[5].id}>; rel="prev"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should correct statuses with min_id", async () => {
|
test("should correct statuses with min_id", async () => {
|
||||||
const response = await fakeRequest(
|
await using client = await generateClient(users[0]);
|
||||||
`/api/v1/timelines/public?limit=20&min_id=${timeline[20].id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getPublicTimeline({
|
||||||
expect(response.headers.get("content-type")).toContain(
|
limit: 5,
|
||||||
"application/json",
|
min_id: timeline[5].id,
|
||||||
);
|
});
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeArrayOfSize(5);
|
||||||
expect(objects.length).toBe(20);
|
for (const [index, status] of data.entries()) {
|
||||||
for (const [index, status] of objects.entries()) {
|
|
||||||
expect(status.account).toBeDefined();
|
expect(status.account).toBeDefined();
|
||||||
expect(status.account.id).toBe(users[0].id);
|
expect(status.account.id).toBe(users[0].id);
|
||||||
expect(status.content).toBeDefined();
|
expect(status.content).toBeDefined();
|
||||||
|
|
@ -186,59 +140,38 @@ describe("/api/v1/timelines/public", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should not return statuses with filtered keywords", async () => {
|
test("should not return statuses with filtered keywords", async () => {
|
||||||
const filterResponse = await fakeRequest("/api/v2/filters", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data, ok } = await client.createFilter(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
["public"],
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Test Filter",
|
||||||
|
"hide",
|
||||||
|
{
|
||||||
|
keywords_attributes: [
|
||||||
|
{
|
||||||
|
keyword: timeline[0].content.slice(4, 20),
|
||||||
|
whole_word: false,
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
],
|
||||||
title: "Test Filter",
|
},
|
||||||
"context[]": "public",
|
);
|
||||||
filter_action: "hide",
|
|
||||||
"keywords_attributes[0][keyword]": timeline[0].content.slice(
|
expect(ok).toBe(true);
|
||||||
4,
|
|
||||||
20,
|
const { data: data2, ok: ok2 } = await client.getPublicTimeline({
|
||||||
),
|
limit: 5,
|
||||||
"keywords_attributes[0][whole_word]": "false",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(filterResponse.status).toBe(200);
|
expect(ok2).toBe(true);
|
||||||
|
expect(data2).toBeArrayOfSize(5);
|
||||||
const response = await fakeRequest(
|
|
||||||
"/api/v1/timelines/public?limit=20",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.headers.get("content-type")).toContain(
|
|
||||||
"application/json",
|
|
||||||
);
|
|
||||||
|
|
||||||
const objects = (await response.json()) as ApiStatus[];
|
|
||||||
|
|
||||||
expect(objects.length).toBe(20);
|
|
||||||
// There should be no element with id of timeline[0].id
|
// There should be no element with id of timeline[0].id
|
||||||
expect(objects).not.toContainEqual(
|
expect(data2).not.toContainEqual(
|
||||||
expect.objectContaining({ id: timeline[0].id }),
|
expect.objectContaining({ id: timeline[0].id }),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Delete filter
|
// Delete filter
|
||||||
const filterDeleteResponse = await fakeRequest(
|
const { ok: ok3 } = await client.deleteFilter(data.id);
|
||||||
`/api/v2/filters/${(await filterResponse.json()).id}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(filterDeleteResponse.status).toBe(204);
|
expect(ok3).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,149 +1,121 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
|
|
||||||
const response = await fakeRequest("/api/v2/filters", {
|
await using client = await generateClient(users[0]);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const { data: filter, ok } = await client.createFilter(
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
["home"],
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Test Filter",
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
expires_in: 86400,
|
||||||
|
keywords_attributes: [{ keyword: "test", whole_word: true }],
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
);
|
||||||
title: "Test Filter",
|
|
||||||
"context[]": "home",
|
|
||||||
filter_action: "warn",
|
|
||||||
expires_in: "86400",
|
|
||||||
"keywords_attributes[0][keyword]": "test",
|
|
||||||
"keywords_attributes[0][whole_word]": "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
const filter = await response.json();
|
|
||||||
expect(filter).toBeObject();
|
expect(filter).toBeObject();
|
||||||
|
expect(filter).toContainKeys(["id", "title"]);
|
||||||
|
expect(filter.title).toBe("Test Filter");
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v2/filters/:id
|
|
||||||
describe("/api/v2/filters/:id", () => {
|
describe("/api/v2/filters/:id", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
await using client = await generateClient();
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getFilter(filter.id);
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should get that filter", async () => {
|
test("should get that filter", async () => {
|
||||||
const response = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getFilter(filter.id);
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toBeObject();
|
expect(data).toBeObject();
|
||||||
expect(json).toContainKeys(["id", "title"]);
|
expect(data).toContainKeys(["id", "title"]);
|
||||||
expect(json.title).toBe("Test Filter");
|
expect(data.title).toBe("Test Filter");
|
||||||
expect(json.context).toEqual(["home"]);
|
expect(data.context).toEqual(["home"]);
|
||||||
expect(json.filter_action).toBe("warn");
|
expect(data.filter_action).toBe("warn");
|
||||||
expect(json.expires_at).toBeString();
|
expect(data.expires_at).toBeString();
|
||||||
expect(json.keywords).toBeArray();
|
expect(data.keywords).toBeArray();
|
||||||
expect(json.keywords).not.toBeEmpty();
|
expect(data.keywords).not.toBeEmpty();
|
||||||
expect(json.keywords[0]).toContainKeys(["keyword", "whole_word"]);
|
expect(data.keywords[0]).toContainKeys(["keyword", "whole_word"]);
|
||||||
expect(json.keywords[0].keyword).toEqual("test");
|
expect(data.keywords[0].keyword).toEqual("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should edit that filter", async () => {
|
test("should edit that filter", async () => {
|
||||||
const response = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
const { data, ok } = await client.updateFilter(filter.id, {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
title: "New Filter",
|
title: "New Filter",
|
||||||
"context[]": "notifications",
|
context: ["notifications"],
|
||||||
filter_action: "hide",
|
filter_action: "hide",
|
||||||
expires_in: "86400",
|
expires_in: 86400,
|
||||||
"keywords_attributes[0][keyword]": "new",
|
keywords_attributes: [
|
||||||
"keywords_attributes[0][id]": filter.keywords[0].id,
|
{
|
||||||
"keywords_attributes[0][whole_word]": "false",
|
id: filter.keywords[0].id,
|
||||||
}),
|
keyword: "new",
|
||||||
|
whole_word: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeObject();
|
||||||
const json = await response.json();
|
expect(data).toContainKeys(["id", "title"]);
|
||||||
expect(json).toBeObject();
|
expect(data.title).toBe("New Filter");
|
||||||
expect(json).toContainKeys(["id", "title"]);
|
expect(data.context).toEqual(["notifications"]);
|
||||||
expect(json.title).toBe("New Filter");
|
expect(data.filter_action).toBe("hide");
|
||||||
expect(json.context).toEqual(["notifications"]);
|
expect(data.expires_at).toBeString();
|
||||||
expect(json.filter_action).toBe("hide");
|
expect(data.keywords).toBeArray();
|
||||||
expect(json.expires_at).toBeString();
|
expect(data.keywords).not.toBeEmpty();
|
||||||
expect(json.keywords).toBeArray();
|
expect(data.keywords[0]).toContainKeys(["keyword", "whole_word"]);
|
||||||
expect(json.keywords).not.toBeEmpty();
|
expect(data.keywords[0].keyword).toEqual("new");
|
||||||
expect(json.keywords[0]).toContainKeys(["keyword", "whole_word"]);
|
|
||||||
expect(json.keywords[0].keyword).toEqual("new");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should delete keyword", async () => {
|
test("should delete keyword", async () => {
|
||||||
const response = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
await using client = await generateClient(users[0]);
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
const { data, ok } = await client.updateFilter(filter.id, {
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
keywords_attributes: [
|
||||||
},
|
// biome-ignore lint/style/useNamingConvention: _destroy is a Mastodon API imposed variable name
|
||||||
body: new URLSearchParams({
|
{ id: filter.keywords[0].id, _destroy: true },
|
||||||
"keywords_attributes[0][id]": filter.keywords[0].id,
|
],
|
||||||
"keywords_attributes[0][_destroy]": "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeObject();
|
||||||
|
expect(data).toContainKeys(["id", "title"]);
|
||||||
|
expect(data.title).toBe("New Filter");
|
||||||
|
|
||||||
const json = await response.json();
|
// Get the filter again and check that the keyword is deleted
|
||||||
expect(json).toBeObject();
|
const { data: data2, ok: ok2 } = await client.getFilter(filter.id);
|
||||||
expect(json.keywords).toBeEmpty();
|
|
||||||
|
|
||||||
// Get the filter again and check
|
expect(ok2).toBe(true);
|
||||||
const getResponse = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
expect(data2.keywords).toBeArray();
|
||||||
headers: {
|
expect(data2.keywords).toBeEmpty();
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getResponse.status).toBe(200);
|
|
||||||
expect((await getResponse.json()).keywords).toBeEmpty();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should delete filter", async () => {
|
test("should delete filter", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[0]);
|
||||||
|
|
||||||
const response = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
const { ok } = await client.deleteFilter(filter.id);
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
expect(ok).toBe(true);
|
||||||
|
|
||||||
// Try to GET the filter again
|
// Try to GET the filter again
|
||||||
const getResponse = await fakeRequest(`/api/v2/filters/${filter.id}`, {
|
const { ok: ok2, raw } = await client.getFilter(filter.id);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getResponse.status).toBe(404);
|
expect(ok2).toBe(false);
|
||||||
|
expect(raw.status).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,54 @@
|
||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest, getTestUsers } from "~/tests/utils";
|
import { generateClient, getTestUsers } from "~/tests/utils";
|
||||||
|
|
||||||
const { tokens, deleteUsers } = await getTestUsers(2);
|
const { users, deleteUsers } = await getTestUsers(2);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await deleteUsers();
|
await deleteUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
// /api/v2/filters
|
|
||||||
describe("/api/v2/filters", () => {
|
describe("/api/v2/filters", () => {
|
||||||
test("should return 401 if not authenticated", async () => {
|
test("should return 401 if not authenticated", async () => {
|
||||||
const response = await fakeRequest("/api/v2/filters");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
const { ok, raw } = await client.getFilters();
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(raw.status).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return user filters (none)", async () => {
|
test("should return user filters (none)", async () => {
|
||||||
const response = await fakeRequest("/api/v2/filters", {
|
await using client = await generateClient(users[0]);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getFilters();
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toBeArray();
|
expect(data).toBeArrayOfSize(0);
|
||||||
expect(json).toBeEmpty();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should create a new filter", async () => {
|
test("should create a new filter", async () => {
|
||||||
const formData = new FormData();
|
await using client = await generateClient(users[0]);
|
||||||
|
|
||||||
formData.append("title", "Test Filter");
|
const { data, ok } = await client.createFilter(
|
||||||
formData.append("context[]", "home");
|
["home"],
|
||||||
formData.append("filter_action", "warn");
|
"Test Filter",
|
||||||
formData.append("expires_in", "86400");
|
"warn",
|
||||||
formData.append("keywords_attributes[0][keyword]", "test");
|
{
|
||||||
formData.append("keywords_attributes[0][whole_word]", "true");
|
expires_in: 86400,
|
||||||
|
keywords_attributes: [{ keyword: "test", whole_word: true }],
|
||||||
const response = await fakeRequest("/api/v2/filters", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${tokens[0].data.accessToken}`,
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
);
|
||||||
title: "Test Filter",
|
|
||||||
"context[]": "home",
|
|
||||||
filter_action: "warn",
|
|
||||||
expires_in: "86400",
|
|
||||||
"keywords_attributes[0][keyword]": "test",
|
|
||||||
"keywords_attributes[0][whole_word]": "true",
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(ok).toBe(true);
|
||||||
|
expect(data).toBeObject();
|
||||||
const json = await response.json();
|
expect(data).toContainKeys(["id", "title"]);
|
||||||
expect(json).toBeObject();
|
expect(data.title).toBe("Test Filter");
|
||||||
expect(json).toContainKeys(["id", "title"]);
|
expect(data.context).toEqual(["home"]);
|
||||||
expect(json.title).toBe("Test Filter");
|
expect(data.filter_action).toBe("warn");
|
||||||
expect(json.context).toEqual(["home"]);
|
expect(data.expires_at).toBeString();
|
||||||
expect(json.filter_action).toBe("warn");
|
expect(data.keywords).toBeArray();
|
||||||
expect(json.expires_at).toBeString();
|
expect(data.keywords).not.toBeEmpty();
|
||||||
expect(json.keywords).toBeArray();
|
expect(data.keywords[0]).toContainKeys(["keyword", "whole_word"]);
|
||||||
expect(json.keywords).not.toBeEmpty();
|
expect(data.keywords[0].keyword).toEqual("test");
|
||||||
expect(json.keywords[0]).toContainKeys(["keyword", "whole_word"]);
|
|
||||||
expect(json.keywords[0].keyword).toEqual("test");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { fakeRequest } from "~/tests/utils";
|
import { generateClient } from "~/tests/utils";
|
||||||
|
|
||||||
// /api/v2/instance
|
// /api/v2/instance
|
||||||
describe("/api/v2/instance", () => {
|
describe("/api/v2/instance", () => {
|
||||||
test("should return instance information", async () => {
|
test("should return instance information", async () => {
|
||||||
const response = await fakeRequest("/api/v2/instance");
|
await using client = await generateClient();
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
const { data, ok } = await client.getInstance();
|
||||||
|
|
||||||
const json = await response.json();
|
expect(ok).toBe(true);
|
||||||
expect(json).toBeObject();
|
expect(data).toBeObject();
|
||||||
expect(json).toContainKeys([
|
expect(data).toContainKeys([
|
||||||
"domain",
|
"domain",
|
||||||
"title",
|
"title",
|
||||||
"version",
|
"version",
|
||||||
|
|
|
||||||
|
|
@ -364,3 +364,176 @@ export const Status = z.object({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Attributes
|
||||||
|
id
|
||||||
|
|
||||||
|
Description: ID of the scheduled status in the database.
|
||||||
|
Type: String (cast from an integer but not guaranteed to be a number)
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
scheduled_at
|
||||||
|
|
||||||
|
Description: The timestamp for when the status will be posted.
|
||||||
|
Type: String (Datetime)
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params
|
||||||
|
|
||||||
|
Description: The parameters that were used when scheduling the status, to be used when the status is posted.
|
||||||
|
Type: Hash
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[text]
|
||||||
|
|
||||||
|
Description: Text to be used as status content.
|
||||||
|
Type: String
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[poll]
|
||||||
|
|
||||||
|
Description: Poll to be attached to the status.
|
||||||
|
Type: nullable Hash
|
||||||
|
Version history:
|
||||||
|
2.8.0 - added
|
||||||
|
params[poll][options[]]
|
||||||
|
|
||||||
|
Description: The poll options to be used.
|
||||||
|
Type: Array of String
|
||||||
|
Version history:
|
||||||
|
2.8.0 - added
|
||||||
|
params[poll][expires_in]
|
||||||
|
|
||||||
|
Description: How many seconds the poll should last before closing.
|
||||||
|
Type: String (cast from integer)
|
||||||
|
Version history:
|
||||||
|
2.8.0 - added
|
||||||
|
params[poll][multiple]
|
||||||
|
|
||||||
|
Description: Whether the poll allows multiple choices.
|
||||||
|
Type: optional Boolean
|
||||||
|
Version history:
|
||||||
|
2.8.0 - added
|
||||||
|
params[poll][hide_totals]
|
||||||
|
|
||||||
|
Description: Whether the poll should hide total votes until after voting has ended.
|
||||||
|
Type: optional Boolean
|
||||||
|
Version history:
|
||||||
|
2.8.0 - added
|
||||||
|
params[media_ids]
|
||||||
|
|
||||||
|
Description: IDs of the MediaAttachments that will be attached to the status.
|
||||||
|
Type: nullable Array of String
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[sensitive]
|
||||||
|
|
||||||
|
Description: Whether the status will be marked as sensitive.
|
||||||
|
Type: nullable Boolean
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[spoiler_text]
|
||||||
|
|
||||||
|
Description: The text of the content warning or summary for the status.
|
||||||
|
Type: nullable String
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[visibility]
|
||||||
|
|
||||||
|
Description: The visibility that the status will have once it is posted.
|
||||||
|
Type: String (Enumerable oneOf)
|
||||||
|
public = Visible to everyone, shown in public timelines.
|
||||||
|
unlisted = Visible to public, but not included in public timelines.
|
||||||
|
private = Visible to followers only, and to any mentioned users.
|
||||||
|
direct = Visible only to mentioned users.
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[in_reply_to_id]
|
||||||
|
|
||||||
|
Description: ID of the Status that will be replied to.
|
||||||
|
Type: nullable Integer
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[language]
|
||||||
|
|
||||||
|
Description: The language that will be used for the status.
|
||||||
|
Type: nullable String (ISO 639-1 two-letter language code)
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[application_id] deprecated
|
||||||
|
|
||||||
|
Description: Internal ID of the Application that posted the status. Provided for historical compatibility only and can be ignored.
|
||||||
|
Type: Integer
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[scheduled_at]
|
||||||
|
|
||||||
|
Description: When the status will be scheduled. This will be null because the status is only scheduled once.
|
||||||
|
Type: nullable Null
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[idempotency]
|
||||||
|
|
||||||
|
Description: Idempotency key to prevent duplicate statuses.
|
||||||
|
Type: nullable String
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
params[with_rate_limit] deprecated
|
||||||
|
|
||||||
|
Description: Whether status creation is subject to rate limiting. Provided for historical compatibility only and can be ignored.
|
||||||
|
Type: Boolean
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
media_attachments
|
||||||
|
Description: Media that will be attached when the status is posted.
|
||||||
|
Type: Array of MediaAttachment
|
||||||
|
Version history:
|
||||||
|
2.7.0 - added
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const ScheduledStatus = z.object({
|
||||||
|
id: Id.openapi({
|
||||||
|
description: "ID of the scheduled status in the database.",
|
||||||
|
example: "2de861d3-a3dd-42ee-ba38-2c7d3f4af588",
|
||||||
|
}),
|
||||||
|
scheduled_at: z.string().datetime().openapi({
|
||||||
|
description: "When the status will be scheduled.",
|
||||||
|
example: "2025-01-07T14:11:00.000Z",
|
||||||
|
}),
|
||||||
|
media_attachments: Status.shape.media_attachments,
|
||||||
|
params: z.object({
|
||||||
|
text: z.string().openapi({
|
||||||
|
description: "Text to be used as status content.",
|
||||||
|
example: "Hello, world!",
|
||||||
|
}),
|
||||||
|
poll: Status.shape.poll,
|
||||||
|
media_ids: z
|
||||||
|
.array(Id)
|
||||||
|
.nullable()
|
||||||
|
.openapi({
|
||||||
|
description:
|
||||||
|
"IDs of the MediaAttachments that will be attached to the status.",
|
||||||
|
example: ["1234567890", "1234567891"],
|
||||||
|
}),
|
||||||
|
sensitive: Status.shape.sensitive,
|
||||||
|
spoiler_text: Status.shape.spoiler_text,
|
||||||
|
visibility: Status.shape.visibility,
|
||||||
|
in_reply_to_id: Status.shape.in_reply_to_id,
|
||||||
|
/** Versia Server API Extension */
|
||||||
|
quote_id: z.string().openapi({
|
||||||
|
description: "ID of the status being quoted.",
|
||||||
|
example: "c5d62a13-f340-4e7d-8942-7fd14be688dc",
|
||||||
|
}),
|
||||||
|
language: Status.shape.language,
|
||||||
|
scheduled_at: z.null().openapi({
|
||||||
|
description:
|
||||||
|
"When the status will be scheduled. This will be null because the status is only scheduled once.",
|
||||||
|
example: null,
|
||||||
|
}),
|
||||||
|
idempotency: z.string().nullable().openapi({
|
||||||
|
description: "Idempotency key to prevent duplicate statuses.",
|
||||||
|
example: "1234567890",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type { Context } from "../schemas/context.ts";
|
||||||
import type { CustomEmoji } from "../schemas/emoji.ts";
|
import type { CustomEmoji } from "../schemas/emoji.ts";
|
||||||
import type { ExtendedDescription } from "../schemas/extended-description.ts";
|
import type { ExtendedDescription } from "../schemas/extended-description.ts";
|
||||||
import type { FamiliarFollowers } from "../schemas/familiar-followers.ts";
|
import type { FamiliarFollowers } from "../schemas/familiar-followers.ts";
|
||||||
|
import type { Filter, FilterKeyword } from "../schemas/filters.ts";
|
||||||
import type { Instance } from "../schemas/instance.ts";
|
import type { Instance } from "../schemas/instance.ts";
|
||||||
import type { Marker } from "../schemas/marker.ts";
|
import type { Marker } from "../schemas/marker.ts";
|
||||||
import type { Notification } from "../schemas/notification.ts";
|
import type { Notification } from "../schemas/notification.ts";
|
||||||
|
|
@ -17,8 +18,13 @@ import type { PrivacyPolicy } from "../schemas/privacy-policy.ts";
|
||||||
import type { WebPushSubscription } from "../schemas/pushsubscription.ts";
|
import type { WebPushSubscription } from "../schemas/pushsubscription.ts";
|
||||||
import type { Relationship } from "../schemas/relationship.ts";
|
import type { Relationship } from "../schemas/relationship.ts";
|
||||||
import type { Report } from "../schemas/report.ts";
|
import type { Report } from "../schemas/report.ts";
|
||||||
|
import type { Rule } from "../schemas/rule.ts";
|
||||||
import type { Search } from "../schemas/search.ts";
|
import type { Search } from "../schemas/search.ts";
|
||||||
import type { Status, StatusSource } from "../schemas/status.ts";
|
import type {
|
||||||
|
ScheduledStatus,
|
||||||
|
Status,
|
||||||
|
StatusSource,
|
||||||
|
} from "../schemas/status.ts";
|
||||||
import type { Tag } from "../schemas/tag.ts";
|
import type { Tag } from "../schemas/tag.ts";
|
||||||
import type { Token } from "../schemas/token.ts";
|
import type { Token } from "../schemas/token.ts";
|
||||||
import type { TermsOfService } from "../schemas/tos.ts";
|
import type { TermsOfService } from "../schemas/tos.ts";
|
||||||
|
|
@ -233,6 +239,41 @@ export class Client extends BaseClient {
|
||||||
return this.post<unknown>("/api/v1/featured_tags", { name }, extra);
|
return this.post<unknown>("/api/v1/featured_tags", { name }, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/v2/filters
|
||||||
|
*
|
||||||
|
* Create a filter.
|
||||||
|
* @param context Filter context.
|
||||||
|
* @param title Filter title.
|
||||||
|
* @param filter_action Filter action.
|
||||||
|
* @param options.expires_in How many seconds from now should the filter expire?
|
||||||
|
* @param options.keywords_attributes Keywords to filter.
|
||||||
|
*/
|
||||||
|
public createFilter(
|
||||||
|
context: z.infer<typeof Filter.shape.context>,
|
||||||
|
title: z.infer<typeof Filter.shape.title>,
|
||||||
|
filter_action: z.infer<typeof Filter.shape.filter_action>,
|
||||||
|
options: Partial<{
|
||||||
|
expires_in: number;
|
||||||
|
keywords_attributes: {
|
||||||
|
keyword: z.infer<typeof FilterKeyword.shape.keyword>;
|
||||||
|
whole_word: z.infer<typeof FilterKeyword.shape.whole_word>;
|
||||||
|
}[];
|
||||||
|
}>,
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Filter>>> {
|
||||||
|
return this.post<z.infer<typeof Filter>>(
|
||||||
|
"/api/v2/filters",
|
||||||
|
{
|
||||||
|
context,
|
||||||
|
title,
|
||||||
|
filter_action,
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: No List schema
|
// FIXME: No List schema
|
||||||
/**
|
/**
|
||||||
* POST /api/v1/lists
|
* POST /api/v1/lists
|
||||||
|
|
@ -350,6 +391,18 @@ export class Client extends BaseClient {
|
||||||
return this.delete(`/api/v1/featured_tags/${id}`, undefined, extra);
|
return this.delete(`/api/v1/featured_tags/${id}`, undefined, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /api/v2/filters/:id
|
||||||
|
*
|
||||||
|
* @param id Target filter ID.
|
||||||
|
*/
|
||||||
|
public deleteFilter(
|
||||||
|
id: string,
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<void>> {
|
||||||
|
return this.delete(`/api/v2/filters/${id}`, undefined, extra);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/v1/lists/:id
|
* DELETE /api/v1/lists/:id
|
||||||
*
|
*
|
||||||
|
|
@ -412,6 +465,28 @@ export class Client extends BaseClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /api/v1/notifications/destroy_multiple
|
||||||
|
*
|
||||||
|
* @param ids Target notification IDs.
|
||||||
|
*/
|
||||||
|
public dismissMultipleNotifications(
|
||||||
|
ids: string[],
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<void>> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
params.append("ids[]", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.delete(
|
||||||
|
`/api/v1/notifications/destroy_multiple?${params.toString()}`,
|
||||||
|
undefined,
|
||||||
|
extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/v1/notifications/:id/dismiss
|
* POST /api/v1/notifications/:id/dismiss
|
||||||
*
|
*
|
||||||
|
|
@ -1122,8 +1197,29 @@ export class Client extends BaseClient {
|
||||||
return this.get<unknown[]>("/api/v1/featured_tags", extra);
|
return this.get<unknown[]>("/api/v1/featured_tags", extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: getFilter
|
/**
|
||||||
// TODO: getFilters
|
* GET /api/v2/filters/:id
|
||||||
|
*
|
||||||
|
* @param id The filter ID.
|
||||||
|
* @return Filter.
|
||||||
|
*/
|
||||||
|
public getFilter(
|
||||||
|
id: string,
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Filter>>> {
|
||||||
|
return this.get<z.infer<typeof Filter>>(`/api/v2/filters/${id}`, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v2/filters
|
||||||
|
*
|
||||||
|
* @return Array of filters.
|
||||||
|
*/
|
||||||
|
public getFilters(
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Filter>[]>> {
|
||||||
|
return this.get<z.infer<typeof Filter>[]>("/api/v2/filters", extra);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v1/follow_requests
|
* GET /api/v1/follow_requests
|
||||||
|
|
@ -1207,7 +1303,7 @@ export class Client extends BaseClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v1/instance
|
* GET /api/v2/instance
|
||||||
*
|
*
|
||||||
* @return Instance.
|
* @return Instance.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1492,7 +1588,7 @@ export class Client extends BaseClient {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
for (const timeline of timelines) {
|
for (const timeline of timelines) {
|
||||||
params.append("timelines[]", timeline);
|
params.append("timeline[]", timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get<z.infer<typeof Marker> | Record<never, never>>(
|
return this.get<z.infer<typeof Marker> | Record<never, never>>(
|
||||||
|
|
@ -1659,6 +1755,8 @@ export class Client extends BaseClient {
|
||||||
since_id: string;
|
since_id: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
only_media: boolean;
|
only_media: boolean;
|
||||||
|
local: boolean;
|
||||||
|
remote: boolean;
|
||||||
}>,
|
}>,
|
||||||
extra?: RequestInit,
|
extra?: RequestInit,
|
||||||
): Promise<Output<z.infer<typeof Status>[]>> {
|
): Promise<Output<z.infer<typeof Status>[]>> {
|
||||||
|
|
@ -1680,6 +1778,12 @@ export class Client extends BaseClient {
|
||||||
if (options.only_media) {
|
if (options.only_media) {
|
||||||
params.set("only_media", "true");
|
params.set("only_media", "true");
|
||||||
}
|
}
|
||||||
|
if (options.local) {
|
||||||
|
params.set("local", "true");
|
||||||
|
}
|
||||||
|
if (options.remote) {
|
||||||
|
params.set("remote", "true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get<z.infer<typeof Status>[]>(
|
return this.get<z.infer<typeof Status>[]>(
|
||||||
|
|
@ -1780,7 +1884,20 @@ export class Client extends BaseClient {
|
||||||
return this.get<z.infer<typeof Role>[]>("/api/v1/roles", extra);
|
return this.get<z.infer<typeof Role>[]>("/api/v1/roles", extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: No ScheduledStatus schema
|
/**
|
||||||
|
* GET /api/v1/instance/rules
|
||||||
|
*
|
||||||
|
* @return Array of rules.
|
||||||
|
*/
|
||||||
|
public getRules(
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Rule>[]>> {
|
||||||
|
return this.get<z.infer<typeof Rule>[]>(
|
||||||
|
"/api/v1/instance/rules",
|
||||||
|
extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v1/scheduled_statuses/:id
|
* GET /api/v1/scheduled_statuses/:id
|
||||||
*
|
*
|
||||||
|
|
@ -1790,11 +1907,13 @@ export class Client extends BaseClient {
|
||||||
public getScheduledStatus(
|
public getScheduledStatus(
|
||||||
id: string,
|
id: string,
|
||||||
extra?: RequestInit,
|
extra?: RequestInit,
|
||||||
): Promise<Output<unknown>> {
|
): Promise<Output<z.infer<typeof ScheduledStatus>>> {
|
||||||
return this.get<unknown>(`/api/v1/scheduled_statuses/${id}`, extra);
|
return this.get<z.infer<typeof ScheduledStatus>>(
|
||||||
|
`/api/v1/scheduled_statuses/${id}`,
|
||||||
|
extra,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: No ScheduledStatus schema
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v1/scheduled_statuses
|
* GET /api/v1/scheduled_statuses
|
||||||
*
|
*
|
||||||
|
|
@ -1812,7 +1931,7 @@ export class Client extends BaseClient {
|
||||||
limit: number;
|
limit: number;
|
||||||
}>,
|
}>,
|
||||||
extra?: RequestInit,
|
extra?: RequestInit,
|
||||||
): Promise<Output<unknown[]>> {
|
): Promise<Output<z.infer<typeof ScheduledStatus>[]>> {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (options) {
|
if (options) {
|
||||||
|
|
@ -1830,7 +1949,7 @@ export class Client extends BaseClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get<unknown[]>(
|
return this.get<z.infer<typeof ScheduledStatus>[]>(
|
||||||
`/api/v1/scheduled_statuses?${params}`,
|
`/api/v1/scheduled_statuses?${params}`,
|
||||||
extra,
|
extra,
|
||||||
);
|
);
|
||||||
|
|
@ -2179,7 +2298,6 @@ export class Client extends BaseClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: No ScheduledStatus schema
|
|
||||||
/**
|
/**
|
||||||
* POST /api/v1/statuses
|
* POST /api/v1/statuses
|
||||||
*
|
*
|
||||||
|
|
@ -2199,7 +2317,7 @@ export class Client extends BaseClient {
|
||||||
*/
|
*/
|
||||||
public postStatus(
|
public postStatus(
|
||||||
status: string,
|
status: string,
|
||||||
options: {
|
options?: {
|
||||||
in_reply_to_id?: string;
|
in_reply_to_id?: string;
|
||||||
quote_id?: string;
|
quote_id?: string;
|
||||||
media_ids?: string[];
|
media_ids?: string[];
|
||||||
|
|
@ -2207,7 +2325,7 @@ export class Client extends BaseClient {
|
||||||
spoiler_text?: string;
|
spoiler_text?: string;
|
||||||
visibility?: z.infer<typeof Status.shape.visibility>;
|
visibility?: z.infer<typeof Status.shape.visibility>;
|
||||||
content_type?: StatusContentType;
|
content_type?: StatusContentType;
|
||||||
scheduled_at?: string;
|
scheduled_at?: Date;
|
||||||
language?: string;
|
language?: string;
|
||||||
local_only?: boolean;
|
local_only?: boolean;
|
||||||
poll?: {
|
poll?: {
|
||||||
|
|
@ -2218,10 +2336,18 @@ export class Client extends BaseClient {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
extra?: RequestInit,
|
extra?: RequestInit,
|
||||||
): Promise<Output<z.infer<typeof Status> | unknown>> {
|
): Promise<
|
||||||
return this.post<z.infer<typeof Status> | unknown>(
|
Output<z.infer<typeof Status> | z.infer<typeof ScheduledStatus>>
|
||||||
|
> {
|
||||||
|
return this.post<
|
||||||
|
z.infer<typeof Status> | z.infer<typeof ScheduledStatus>
|
||||||
|
>(
|
||||||
"/api/v1/statuses",
|
"/api/v1/statuses",
|
||||||
{ status, ...options },
|
{
|
||||||
|
status,
|
||||||
|
...options,
|
||||||
|
scheduled_at: options?.scheduled_at?.toISOString(),
|
||||||
|
},
|
||||||
extra,
|
extra,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2455,15 +2581,31 @@ export class Client extends BaseClient {
|
||||||
};
|
};
|
||||||
}>,
|
}>,
|
||||||
extra?: RequestInit,
|
extra?: RequestInit,
|
||||||
): Promise<Output<z.infer<typeof Marker>>> {
|
): Promise<
|
||||||
return this.post<z.infer<typeof Marker>>(
|
Output<{
|
||||||
"/api/v1/markers",
|
home?: z.infer<typeof Marker>;
|
||||||
options,
|
notifications?: z.infer<typeof Marker>;
|
||||||
extra,
|
}>
|
||||||
|
> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (options.home) {
|
||||||
|
params.set("home[last_read_id]", options.home.last_read_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.notifications) {
|
||||||
|
params.set(
|
||||||
|
"notifications[last_read_id]",
|
||||||
|
options.notifications.last_read_id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: No ScheduledStatus schema
|
return this.post<{
|
||||||
|
home?: z.infer<typeof Marker>;
|
||||||
|
notifications?: z.infer<typeof Marker>;
|
||||||
|
}>(`/api/v1/markers?${params}`, undefined, extra);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/v1/scheduled_statuses/:id
|
* PUT /api/v1/scheduled_statuses/:id
|
||||||
*
|
*
|
||||||
|
|
@ -2475,8 +2617,8 @@ export class Client extends BaseClient {
|
||||||
id: string,
|
id: string,
|
||||||
scheduled_at?: Date,
|
scheduled_at?: Date,
|
||||||
extra?: RequestInit,
|
extra?: RequestInit,
|
||||||
): Promise<Output<unknown>> {
|
): Promise<Output<z.infer<typeof ScheduledStatus>>> {
|
||||||
return this.put<unknown>(
|
return this.put<z.infer<typeof ScheduledStatus>>(
|
||||||
`/api/v1/scheduled_statuses/${id}`,
|
`/api/v1/scheduled_statuses/${id}`,
|
||||||
{ scheduled_at: scheduled_at?.toISOString() },
|
{ scheduled_at: scheduled_at?.toISOString() },
|
||||||
extra,
|
extra,
|
||||||
|
|
@ -2648,7 +2790,13 @@ export class Client extends BaseClient {
|
||||||
): Promise<Output<z.infer<typeof WebPushSubscription>>> {
|
): Promise<Output<z.infer<typeof WebPushSubscription>>> {
|
||||||
return this.post<z.infer<typeof WebPushSubscription>>(
|
return this.post<z.infer<typeof WebPushSubscription>>(
|
||||||
"/api/v1/push/subscription",
|
"/api/v1/push/subscription",
|
||||||
{ subscription, data },
|
{
|
||||||
|
subscription,
|
||||||
|
policy: data?.policy,
|
||||||
|
data: {
|
||||||
|
alerts: data?.alerts,
|
||||||
|
},
|
||||||
|
},
|
||||||
extra,
|
extra,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2902,7 +3050,40 @@ export class Client extends BaseClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: updateFilter
|
/**
|
||||||
|
* PUT /api/v2/filters/:id
|
||||||
|
*
|
||||||
|
* @param id Target filter ID.
|
||||||
|
* @param options.title New filter title.
|
||||||
|
* @param options.context New filter context.
|
||||||
|
* @param options.filter_action New filter action.
|
||||||
|
* @param options.expires_in New filter expiration.
|
||||||
|
* @param options.keywords_attributes New filter keywords.
|
||||||
|
* @return Filter.
|
||||||
|
*/
|
||||||
|
public updateFilter(
|
||||||
|
id: string,
|
||||||
|
options: Partial<{
|
||||||
|
title: string;
|
||||||
|
context: string[];
|
||||||
|
filter_action: string;
|
||||||
|
expires_in: number;
|
||||||
|
keywords_attributes: Partial<{
|
||||||
|
id: string;
|
||||||
|
keyword: string;
|
||||||
|
whole_word: boolean;
|
||||||
|
_destroy: boolean;
|
||||||
|
}>[];
|
||||||
|
}>,
|
||||||
|
extra?: RequestInit,
|
||||||
|
): Promise<Output<z.infer<typeof Filter>>> {
|
||||||
|
return this.put<z.infer<typeof Filter>>(
|
||||||
|
`/api/v2/filters/${id}`,
|
||||||
|
options,
|
||||||
|
extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: No List schema
|
// FIXME: No List schema
|
||||||
/**
|
/**
|
||||||
* PUT /api/v1/lists/:id
|
* PUT /api/v1/lists/:id
|
||||||
|
|
@ -2988,7 +3169,7 @@ export class Client extends BaseClient {
|
||||||
): Promise<Output<z.infer<typeof WebPushSubscription>>> {
|
): Promise<Output<z.infer<typeof WebPushSubscription>>> {
|
||||||
return this.put<z.infer<typeof WebPushSubscription>>(
|
return this.put<z.infer<typeof WebPushSubscription>>(
|
||||||
"/api/v1/push/subscription",
|
"/api/v1/push/subscription",
|
||||||
{ ...data, policy },
|
{ data, policy },
|
||||||
extra,
|
extra,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ export const generateClient = async (
|
||||||
): Promise<
|
): Promise<
|
||||||
VersiaClient & {
|
VersiaClient & {
|
||||||
[Symbol.asyncDispose](): Promise<void>;
|
[Symbol.asyncDispose](): Promise<void>;
|
||||||
|
dbToken: Token;
|
||||||
}
|
}
|
||||||
> => {
|
> => {
|
||||||
const token = user
|
const token = user
|
||||||
|
|
@ -69,8 +70,12 @@ export const generateClient = async (
|
||||||
await token?.delete();
|
await token?.delete();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error More monkeypatching
|
||||||
|
client.dbToken = token;
|
||||||
|
|
||||||
return client as VersiaClient & {
|
return client as VersiaClient & {
|
||||||
[Symbol.asyncDispose](): Promise<void>;
|
[Symbol.asyncDispose](): Promise<void>;
|
||||||
|
dbToken: Token;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const deleteOldTestUsers = async (): Promise<void> => {
|
export const deleteOldTestUsers = async (): Promise<void> => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue