mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
Add new tests to server routes
This commit is contained in:
parent
6263c667e8
commit
6b3c604c33
|
|
@ -1,6 +1,11 @@
|
||||||
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
|
import type { token } from "~drizzle/schema";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of token.
|
* The type of token.
|
||||||
*/
|
*/
|
||||||
export enum TokenType {
|
export enum TokenType {
|
||||||
BEARER = "Bearer",
|
BEARER = "Bearer",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Token = InferSelectModel<typeof token>;
|
||||||
|
|
|
||||||
|
|
@ -563,6 +563,7 @@ export const createNewLocalUser = async (data: {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
header?: string;
|
header?: string;
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
|
skipPasswordHash?: boolean;
|
||||||
}): Promise<UserWithRelations | null> => {
|
}): Promise<UserWithRelations | null> => {
|
||||||
const keys = await generateUserKeys();
|
const keys = await generateUserKeys();
|
||||||
|
|
||||||
|
|
@ -572,7 +573,9 @@ export const createNewLocalUser = async (data: {
|
||||||
.values({
|
.values({
|
||||||
username: data.username,
|
username: data.username,
|
||||||
displayName: data.display_name ?? data.username,
|
displayName: data.display_name ?? data.username,
|
||||||
password: await Bun.password.hash(data.password),
|
password: data.skipPasswordHash
|
||||||
|
? data.password
|
||||||
|
: await Bun.password.hash(data.password),
|
||||||
email: data.email,
|
email: data.email,
|
||||||
note: data.bio ?? "",
|
note: data.bio ?? "",
|
||||||
avatar: data.avatar ?? config.defaults.avatar,
|
avatar: data.avatar ?? config.defaults.avatar,
|
||||||
|
|
|
||||||
206
server/api/api/v1/timelines/home.test.ts
Normal file
206
server/api/api/v1/timelines/home.test.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
|
import {
|
||||||
|
deleteOldTestUsers,
|
||||||
|
getTestStatuses,
|
||||||
|
getTestUsers,
|
||||||
|
sendTestRequest,
|
||||||
|
} from "~tests/utils";
|
||||||
|
import { config } from "~index";
|
||||||
|
import { meta } from "./home";
|
||||||
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
|
await deleteOldTestUsers();
|
||||||
|
|
||||||
|
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||||
|
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await deleteUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(meta.route, () => {
|
||||||
|
test("should return 401 if not authenticated", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(new URL(meta.route, config.http.base_url)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 400 if limit is less than 1", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=0`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 400 if limit is greater than 80", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=100`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correctly parse limit", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=5`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
|
const objects = (await response.json()) as APIStatus[];
|
||||||
|
|
||||||
|
expect(objects.length).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 200 with statuses", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(new URL(meta.route, config.http.base_url), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
|
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.id).toBe(users[0].id);
|
||||||
|
expect(status.content).toBeDefined();
|
||||||
|
expect(status.created_at).toBeDefined();
|
||||||
|
expect(status.id).toBe(timeline[index].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("should paginate properly", async () => {
|
||||||
|
test("should send correct Link header", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=20`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.headers.get("link")).toBe(
|
||||||
|
`<${config.http.base_url}/api/v1/timelines/home?limit=20&max_id=${timeline[19].id}>; rel="next"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correct statuses with max", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(
|
||||||
|
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe(
|
||||||
|
"application/json",
|
||||||
|
);
|
||||||
|
|
||||||
|
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.id).toBe(users[0].id);
|
||||||
|
expect(status.content).toBeDefined();
|
||||||
|
expect(status.created_at).toBeDefined();
|
||||||
|
expect(status.id).toBe(timeline[index + 20].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should send correct Link prev header", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(
|
||||||
|
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.headers.get("link")).toInclude(
|
||||||
|
`${config.http.base_url}/api/v1/timelines/home?limit=20&min_id=${timeline[20].id}>; rel="prev"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correct statuses with min_id", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(
|
||||||
|
`${meta.route}?limit=20&min_id=${timeline[20].id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe(
|
||||||
|
"application/json",
|
||||||
|
);
|
||||||
|
|
||||||
|
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.id).toBe(users[0].id);
|
||||||
|
expect(status.content).toBeDefined();
|
||||||
|
expect(status.created_at).toBeDefined();
|
||||||
|
expect(status.id).toBe(timeline[index].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { apiRoute, applyConfig } from "@api";
|
import { apiRoute, applyConfig } from "@api";
|
||||||
import { errorResponse, jsonResponse } from "@response";
|
import { errorResponse, jsonResponse } from "@response";
|
||||||
import { fetchTimeline } from "@timelines";
|
import { fetchTimeline } from "@timelines";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import {
|
import {
|
||||||
type StatusWithRelations,
|
type StatusWithRelations,
|
||||||
statusToAPI,
|
statusToAPI,
|
||||||
findManyStatuses,
|
findManyStatuses,
|
||||||
} from "~database/entities/Status";
|
} from "~database/entities/Status";
|
||||||
import { statusAndUserRelations } from "~database/entities/relations";
|
|
||||||
import { db } from "~drizzle/db";
|
import { db } from "~drizzle/db";
|
||||||
|
|
||||||
export const meta = applyConfig({
|
export const meta = applyConfig({
|
||||||
|
|
@ -54,12 +52,13 @@ export default apiRoute<{
|
||||||
{
|
{
|
||||||
// @ts-expect-error Yes I KNOW the types are wrong
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
where: (status, { lt, gte, gt, and, or, eq, inArray, sql }) =>
|
where: (status, { lt, gte, gt, and, or, eq, inArray, sql }) =>
|
||||||
or(
|
and(
|
||||||
and(
|
and(
|
||||||
max_id ? lt(status.id, max_id) : undefined,
|
max_id ? lt(status.id, max_id) : undefined,
|
||||||
since_id ? gte(status.id, since_id) : undefined,
|
since_id ? gte(status.id, since_id) : undefined,
|
||||||
min_id ? gt(status.id, min_id) : undefined,
|
min_id ? gt(status.id, min_id) : undefined,
|
||||||
),
|
),
|
||||||
|
or(
|
||||||
eq(status.authorId, user.id),
|
eq(status.authorId, user.id),
|
||||||
/* inArray(
|
/* inArray(
|
||||||
status.authorId,
|
status.authorId,
|
||||||
|
|
@ -72,6 +71,7 @@ export default apiRoute<{
|
||||||
// WHERE format (... = ...)
|
// WHERE format (... = ...)
|
||||||
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${status.authorId} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."following" = true)`,
|
sql`EXISTS (SELECT 1 FROM "Relationship" WHERE "Relationship"."subjectId" = ${status.authorId} AND "Relationship"."ownerId" = ${user.id} AND "Relationship"."following" = true)`,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
limit: Number(limit),
|
limit: Number(limit),
|
||||||
// @ts-expect-error Yes I KNOW the types are wrong
|
// @ts-expect-error Yes I KNOW the types are wrong
|
||||||
orderBy: (status, { desc }) => desc(status.id),
|
orderBy: (status, { desc }) => desc(status.id),
|
||||||
|
|
|
||||||
198
server/api/api/v1/timelines/public.test.ts
Normal file
198
server/api/api/v1/timelines/public.test.ts
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
|
import {
|
||||||
|
deleteOldTestUsers,
|
||||||
|
getTestStatuses,
|
||||||
|
getTestUsers,
|
||||||
|
sendTestRequest,
|
||||||
|
} from "~tests/utils";
|
||||||
|
import { config } from "~index";
|
||||||
|
import { meta } from "./public";
|
||||||
|
import type { APIStatus } from "~types/entities/status";
|
||||||
|
|
||||||
|
await deleteOldTestUsers();
|
||||||
|
|
||||||
|
const { users, tokens, deleteUsers } = await getTestUsers(5);
|
||||||
|
const timeline = (await getTestStatuses(40, users[0])).toReversed();
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await deleteUsers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(meta.route, () => {
|
||||||
|
test("should return 400 if limit is less than 1", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=0`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 400 if limit is greater than 80", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=100`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correctly parse limit", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=5`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
|
const objects = (await response.json()) as APIStatus[];
|
||||||
|
|
||||||
|
expect(objects.length).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return 200 with statuses", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(new URL(meta.route, config.http.base_url), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe("application/json");
|
||||||
|
|
||||||
|
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.id).toBe(users[0].id);
|
||||||
|
expect(status.content).toBeDefined();
|
||||||
|
expect(status.created_at).toBeDefined();
|
||||||
|
expect(status.id).toBe(timeline[index].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("should paginate properly", async () => {
|
||||||
|
test("should send correct Link header", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(`${meta.route}?limit=20`, config.http.base_url),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.headers.get("link")).toBe(
|
||||||
|
`<${config.http.base_url}/api/v1/timelines/public?limit=20&max_id=${timeline[19].id}>; rel="next"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correct statuses with max", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(
|
||||||
|
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe(
|
||||||
|
"application/json",
|
||||||
|
);
|
||||||
|
|
||||||
|
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.id).toBe(users[0].id);
|
||||||
|
expect(status.content).toBeDefined();
|
||||||
|
expect(status.created_at).toBeDefined();
|
||||||
|
expect(status.id).toBe(timeline[index + 20].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should send correct Link prev header", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(
|
||||||
|
`${meta.route}?limit=20&max_id=${timeline[19].id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.headers.get("link")).toInclude(
|
||||||
|
`${config.http.base_url}/api/v1/timelines/public?limit=20&min_id=${timeline[20].id}>; rel="prev"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should correct statuses with min_id", async () => {
|
||||||
|
const response = await sendTestRequest(
|
||||||
|
new Request(
|
||||||
|
new URL(
|
||||||
|
`${meta.route}?limit=20&min_id=${timeline[20].id}`,
|
||||||
|
config.http.base_url,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("content-type")).toBe(
|
||||||
|
"application/json",
|
||||||
|
);
|
||||||
|
|
||||||
|
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.id).toBe(users[0].id);
|
||||||
|
expect(status.content).toBeDefined();
|
||||||
|
expect(status.created_at).toBeDefined();
|
||||||
|
expect(status.id).toBe(timeline[index].id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
|
import {
|
||||||
|
createNewLocalUser,
|
||||||
|
type User,
|
||||||
|
type UserWithRelations,
|
||||||
|
} from "~database/entities/User";
|
||||||
|
import { randomBytes } from "node:crypto";
|
||||||
import { server } from "~index";
|
import { server } from "~index";
|
||||||
|
import { db } from "~drizzle/db";
|
||||||
|
import { status, token, user } from "~drizzle/schema";
|
||||||
|
import { inArray, like } from "drizzle-orm";
|
||||||
|
import type { Status } from "~database/entities/Status";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This allows us to send a test request to the server even when it isnt running
|
* This allows us to send a test request to the server even when it isnt running
|
||||||
|
|
@ -13,3 +23,86 @@ export async function sendTestRequest(req: Request) {
|
||||||
export function wrapRelativeUrl(url: string, base_url: string) {
|
export function wrapRelativeUrl(url: string, base_url: string) {
|
||||||
return new URL(url, base_url);
|
return new URL(url, base_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const deleteOldTestUsers = async () => {
|
||||||
|
// Deletes all users that match the test username (test-<32 random characters>)
|
||||||
|
await db.delete(user).where(like(user.username, "test-%"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTestUsers = async (count: number) => {
|
||||||
|
const users: UserWithRelations[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const user = await createNewLocalUser({
|
||||||
|
username: `test-${randomBytes(32).toString("hex")}`,
|
||||||
|
email: `${randomBytes(32).toString("hex")}@test.com`,
|
||||||
|
password: randomBytes(32).toString("hex"),
|
||||||
|
skipPasswordHash: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("Failed to create test user");
|
||||||
|
}
|
||||||
|
|
||||||
|
users.push(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await db
|
||||||
|
.insert(token)
|
||||||
|
.values(
|
||||||
|
users.map((u) => ({
|
||||||
|
accessToken: randomBytes(32).toString("hex"),
|
||||||
|
tokenType: "bearer",
|
||||||
|
userId: u.id,
|
||||||
|
applicationId: null,
|
||||||
|
code: randomBytes(32).toString("hex"),
|
||||||
|
scope: "read write follow push",
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
return {
|
||||||
|
users,
|
||||||
|
tokens,
|
||||||
|
deleteUsers: async () => {
|
||||||
|
await db.delete(user).where(
|
||||||
|
inArray(
|
||||||
|
user.id,
|
||||||
|
users.map((u) => u.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTestStatuses = async (
|
||||||
|
count: number,
|
||||||
|
user: User,
|
||||||
|
partial?: Partial<Status>,
|
||||||
|
) => {
|
||||||
|
const statuses: Status[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const newStatus = (
|
||||||
|
await db
|
||||||
|
.insert(status)
|
||||||
|
.values({
|
||||||
|
content: `${i} ${randomBytes(32).toString("hex")}`,
|
||||||
|
authorId: user.id,
|
||||||
|
sensitive: false,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
visibility: "public",
|
||||||
|
...partial,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (!newStatus) {
|
||||||
|
throw new Error("Failed to create test status");
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses.push(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses.toSorted((a, b) => a.id.localeCompare(b.id));
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,13 @@ export async function fetchTimeline<T extends User | Status | Notification>(
|
||||||
const urlWithoutQuery = req.url.split("?")[0];
|
const urlWithoutQuery = req.url.split("?")[0];
|
||||||
// Add prev link
|
// Add prev link
|
||||||
linkHeader.push(
|
linkHeader.push(
|
||||||
`<${urlWithoutQuery}?min_id=${objects[0].id}>; rel="prev"`,
|
`<${urlWithoutQuery}?limit=${args?.limit ?? 20}&min_id=${
|
||||||
|
objects[0].id
|
||||||
|
}>; rel="prev"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (objects.length >= (Number(args?.limit) ?? 20)) {
|
||||||
objects.length < (Number(args?.limit) ?? Number.POSITIVE_INFINITY)
|
|
||||||
) {
|
|
||||||
// Check if there are statuses after the last one
|
// Check if there are statuses after the last one
|
||||||
// @ts-expect-error hack again
|
// @ts-expect-error hack again
|
||||||
const objectsAfter = await model({
|
const objectsAfter = await model({
|
||||||
|
|
@ -59,7 +59,9 @@ export async function fetchTimeline<T extends User | Status | Notification>(
|
||||||
const urlWithoutQuery = req.url.split("?")[0];
|
const urlWithoutQuery = req.url.split("?")[0];
|
||||||
// Add next link
|
// Add next link
|
||||||
linkHeader.push(
|
linkHeader.push(
|
||||||
`<${urlWithoutQuery}?max_id=${objectsAfter[0].id}>; rel="next"`,
|
`<${urlWithoutQuery}?limit=${args?.limit ?? 20}&max_id=${
|
||||||
|
objects.at(-1)?.id
|
||||||
|
}>; rel="next"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue