More API tests, fixes

This commit is contained in:
Jesse Wierzbinski 2023-10-22 15:47:04 -10:00
parent 932fc3e4f5
commit d05b077df1
No known key found for this signature in database
GPG key ID: F9A1E418934E40B0
10 changed files with 90 additions and 28 deletions

View file

@ -88,6 +88,7 @@ Working endpoints are:
- `/api/v1/accounts/verify_credentials` - `/api/v1/accounts/verify_credentials`
- `/api/v1/accounts/familiar_followers` - `/api/v1/accounts/familiar_followers`
- `/api/v1/statuses/:id` (`GET`, `DELETE`) - `/api/v1/statuses/:id` (`GET`, `DELETE`)
- `/api/v1/statuses/:id/context`
- `/api/v1/statuses` - `/api/v1/statuses`
- `/api/v1/timelines/public` - `/api/v1/timelines/public`
- `/api/v1/apps` - `/api/v1/apps`

View file

@ -21,6 +21,21 @@ import { Instance } from "./Instance";
const config = getConfig(); const config = getConfig();
export const statusRelations = [
"account",
"reblog",
"object",
"in_reply_to_post",
"instance",
"in_reply_to_account",
"in_reply_to_post.account",
"application",
"emojis",
"mentions",
"likes",
"announces",
];
/** /**
* Represents a status (i.e. a post) * Represents a status (i.e. a post)
*/ */
@ -57,8 +72,9 @@ export class Status extends BaseEntity {
*/ */
@ManyToOne(() => Status, status => status.id, { @ManyToOne(() => Status, status => status.id, {
nullable: true, nullable: true,
onDelete: "SET NULL",
}) })
reblog?: Status; reblog?: Status | null;
/** /**
* The raw object associated with this status. * The raw object associated with this status.
@ -94,6 +110,7 @@ export class Status extends BaseEntity {
*/ */
@ManyToOne(() => Status, { @ManyToOne(() => Status, {
nullable: true, nullable: true,
onDelete: "SET NULL",
}) })
in_reply_to_post!: Status | null; in_reply_to_post!: Status | null;
@ -229,9 +246,7 @@ export class Status extends BaseEntity {
where: { where: {
id: id, id: id,
}, },
relations: { relations: statusRelations,
in_reply_to_post: true,
},
}); });
if (currentStatus) { if (currentStatus) {
@ -279,9 +294,7 @@ export class Status extends BaseEntity {
id: status.id, id: status.id,
}, },
}, },
relations: { relations: statusRelations,
in_reply_to_post: true,
},
}); });
for (const status of currentStatus) { for (const status of currentStatus) {
@ -443,6 +456,8 @@ export class Status extends BaseEntity {
return { return {
...(await this.object.toAPI()), ...(await this.object.toAPI()),
id: this.id, id: this.id,
in_reply_to_id: this.in_reply_to_post?.id || null,
in_reply_to_account_id: this.in_reply_to_post?.account.id || null,
}; };
} }
} }

View file

@ -21,7 +21,7 @@ import {
} from "activitypub-types"; } from "activitypub-types";
import { RawObject } from "./RawObject"; import { RawObject } from "./RawObject";
import { Token } from "./Token"; import { Token } from "./Token";
import { Status } from "./Status"; import { Status, statusRelations } from "./Status";
import { APISource } from "~types/entities/source"; import { APISource } from "~types/entities/source";
import { Relationship } from "./Relationship"; import { Relationship } from "./Relationship";
import { Instance } from "./Instance"; import { Instance } from "./Instance";
@ -368,9 +368,7 @@ export class User extends BaseEntity {
id: this.id, id: this.id,
}, },
}, },
relations: { relations: statusRelations,
object: true,
},
}); });
// Delete both // Delete both

View file

@ -1,6 +1,6 @@
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Status } from "~database/entities/Status"; import { Status, statusRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { applyConfig } from "@api"; import { applyConfig } from "@api";
@ -60,7 +60,7 @@ export default async (
}, },
isReblog: exclude_reblogs ? true : undefined, isReblog: exclude_reblogs ? true : undefined,
}, },
relations: ["account", "emojis", "announces", "likes", "object"], relations: statusRelations,
order: { order: {
created_at: "DESC", created_at: "DESC",
}, },

View file

@ -1,7 +1,7 @@
import { applyConfig } from "@api"; import { applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Status } from "~database/entities/Status"; import { Status, statusRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
@ -32,8 +32,11 @@ export default async (
let foundStatus: Status | null; let foundStatus: Status | null;
try { try {
foundStatus = await Status.findOneBy({ foundStatus = await Status.findOne({
id, where: {
id,
},
relations: statusRelations,
}); });
} catch (e) { } catch (e) {
return errorResponse("Invalid ID", 404); return errorResponse("Invalid ID", 404);

View file

@ -1,7 +1,7 @@
import { applyConfig } from "@api"; import { applyConfig } from "@api";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun"; import { MatchedRoute } from "bun";
import { Status } from "~database/entities/Status"; import { Status, statusRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
@ -35,7 +35,7 @@ export default async (
where: { where: {
id, id,
}, },
relations: ["account", "object"], relations: statusRelations,
}); });
} catch (e) { } catch (e) {
return errorResponse("Invalid ID", 404); return errorResponse("Invalid ID", 404);

View file

@ -11,7 +11,7 @@ import { sanitize } from "isomorphic-dompurify";
import { parse } from "marked"; import { parse } from "marked";
import { Application } from "~database/entities/Application"; import { Application } from "~database/entities/Application";
import { RawObject } from "~database/entities/RawObject"; import { RawObject } from "~database/entities/RawObject";
import { Status } from "~database/entities/Status"; import { Status, statusRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
@ -132,9 +132,7 @@ export default async (req: Request): Promise<Response> => {
where: { where: {
id: in_reply_to_id, id: in_reply_to_id,
}, },
relations: { relations: statusRelations,
account: true,
},
}); });
replyUser = replyStatus?.account || null; replyUser = replyStatus?.account || null;

View file

@ -3,7 +3,7 @@ import { applyConfig } from "@api";
import { parseRequest } from "@request"; import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { FindManyOptions } from "typeorm"; import { FindManyOptions } from "typeorm";
import { Status } from "~database/entities/Status"; import { Status, statusRelations } from "~database/entities/Status";
import { User } from "~database/entities/User"; import { User } from "~database/entities/User";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
@ -60,7 +60,7 @@ export default async (req: Request): Promise<Response> => {
created_at: "DESC", created_at: "DESC",
}, },
take: limit, take: limit,
relations: ["object"], relations: statusRelations,
}; };
if (max_id) { if (max_id) {

View file

@ -3,7 +3,7 @@ import { applyConfig } from "@api";
import { parseRequest } from "@request"; import { parseRequest } from "@request";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { FindManyOptions, IsNull, Not } from "typeorm"; import { FindManyOptions, IsNull, Not } from "typeorm";
import { Status } from "~database/entities/Status"; import { Status, statusRelations } from "~database/entities/Status";
import { APIRouteMeta } from "~types/api"; import { APIRouteMeta } from "~types/api";
export const meta: APIRouteMeta = applyConfig({ export const meta: APIRouteMeta = applyConfig({
@ -56,7 +56,7 @@ export default async (req: Request): Promise<Response> => {
created_at: "DESC", created_at: "DESC",
}, },
take: limit, take: limit,
relations: ["object"], relations: statusRelations,
}; };
if (max_id) { if (max_id) {

View file

@ -20,6 +20,7 @@ let token: Token;
let user: User; let user: User;
let user2: User; let user2: User;
let status: APIStatus | null = null; let status: APIStatus | null = null;
let status2: APIStatus | null = null;
describe("API Tests", () => { describe("API Tests", () => {
beforeAll(async () => { beforeAll(async () => {
@ -153,6 +154,52 @@ describe("API Tests", () => {
expect(status.in_reply_to_id).toBeNull(); expect(status.in_reply_to_id).toBeNull();
expect(status.in_reply_to_account_id).toBeNull(); expect(status.in_reply_to_account_id).toBeNull();
}); });
test("should create a new status in reply to the previous one", async () => {
const response = await fetch(
`${config.http.base_url}/api/v1/statuses`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token.access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
status: "This is a reply!",
visibility: "public",
in_reply_to_id: status?.id,
}),
}
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(
"application/json"
);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
status2 = (await response.json()) as APIStatus;
expect(status2.content).toBe("This is a reply!");
expect(status2.visibility).toBe("public");
expect(status2.account.id).toBe(user.id);
expect(status2.replies_count).toBe(0);
expect(status2.favourites_count).toBe(0);
expect(status2.reblogged).toBe(false);
expect(status2.favourited).toBe(false);
expect(status2.media_attachments).toEqual([]);
expect(status2.mentions).toEqual([]);
expect(status2.tags).toEqual([]);
expect(status2.sensitive).toBe(false);
expect(status2.spoiler_text).toBe("");
expect(status2.language).toBeNull();
expect(status2.pinned).toBe(false);
expect(status2.visibility).toBe("public");
expect(status2.card).toBeNull();
expect(status2.poll).toBeNull();
expect(status2.emojis).toEqual([]);
expect(status2.in_reply_to_id).toEqual(status?.id);
expect(status2.in_reply_to_account_id).toEqual(user.id);
});
}); });
describe("GET /api/v1/timelines/public", () => { describe("GET /api/v1/timelines/public", () => {
@ -270,9 +317,9 @@ describe("API Tests", () => {
const statuses = (await response.json()) as APIStatus[]; const statuses = (await response.json()) as APIStatus[];
expect(statuses.length).toBe(1); expect(statuses.length).toBe(2);
const status1 = statuses[0]; const status1 = statuses[1];
// Basic validation // Basic validation
expect(status1.content).toBe("Hello, world!"); expect(status1.content).toBe("Hello, world!");