Test Note object federation

This commit is contained in:
Jesse Wierzbinski 2023-09-13 17:39:11 -10:00
parent 4ced6744fa
commit d92764d81a
No known key found for this signature in database
GPG key ID: F9A1E418934E40B0
7 changed files with 131 additions and 4 deletions

View file

@ -2,6 +2,7 @@ import {
BaseEntity, BaseEntity,
Column, Column,
Entity, Entity,
JoinTable,
ManyToMany, ManyToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from "typeorm"; } from "typeorm";
@ -18,10 +19,11 @@ export class RawActivity extends BaseEntity {
@PrimaryGeneratedColumn("uuid") @PrimaryGeneratedColumn("uuid")
id!: string; id!: string;
@Column("json") @Column("jsonb")
data!: APActivity; data!: APActivity;
// Any associated objects (there is typically only one) // Any associated objects (there is typically only one)
@ManyToMany(() => RawObject, object => object.id) @ManyToMany(() => RawObject, object => object.id)
@JoinTable()
objects!: RawObject[]; objects!: RawObject[];
} }

View file

@ -11,6 +11,6 @@ export class RawObject extends BaseEntity {
@PrimaryGeneratedColumn("uuid") @PrimaryGeneratedColumn("uuid")
id!: string; id!: string;
@Column("json") @Column("jsonb")
data!: APObject; data!: APObject;
} }

View file

@ -19,6 +19,8 @@ Bun.serve({
async fetch(req) { async fetch(req) {
const matchedRoute = router.match(req); const matchedRoute = router.match(req);
console.log(req.url);
if (matchedRoute) { if (matchedRoute) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return (await import(matchedRoute.filePath)).default( return (await import(matchedRoute.filePath)).default(

View file

@ -11,7 +11,7 @@ export default async (
req: Request, req: Request,
matchedRoute: MatchedRoute matchedRoute: MatchedRoute
): Promise<Response> => { ): Promise<Response> => {
const username = matchedRoute.params.username; const username = matchedRoute.params.username.split("@")[0];
const user = await User.findOneBy({ username }); const user = await User.findOneBy({ username });

View file

@ -12,7 +12,7 @@ export default async (
req: Request, req: Request,
matchedRoute: MatchedRoute matchedRoute: MatchedRoute
): Promise<Response> => { ): Promise<Response> => {
const username = matchedRoute.params.username; const username = matchedRoute.params.username.split("@")[0];
const page = Boolean(matchedRoute.query.page || "false"); const page = Boolean(matchedRoute.query.page || "false");
const min_id = matchedRoute.query.min_id || false; const min_id = matchedRoute.query.min_id || false;
const max_id = matchedRoute.query.max_id || false; const max_id = matchedRoute.query.max_id || false;

123
tests/inbox.test.ts Normal file
View file

@ -0,0 +1,123 @@
import { getConfig } from "@config";
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { AppDataSource } from "~database/datasource";
import { RawActivity } from "~database/entities/RawActivity";
import { Token } from "~database/entities/Token";
import { User } from "~database/entities/User";
const config = getConfig();
beforeAll(async () => {
if (!AppDataSource.isInitialized) await AppDataSource.initialize();
// Initialize test user
const user = new User();
user.email = "test@test.com";
user.username = "test";
user.password = await Bun.password.hash("test");
user.display_name = "";
user.bio = "";
await user.save();
});
describe("POST /@test/inbox", () => {
test("should store a new Note object", async () => {
const response = await fetch(
`${config.http.base_url}:${config.http.port}/@test/inbox/`,
{
method: "POST",
headers: {
"Content-Type": "application/activity+json",
},
body: JSON.stringify({
"@context": "https://www.w3.org/ns/activitystreams",
type: "Create",
id: "https://example.com/notes/1/activity",
actor: `${config.http.base_url}:${config.http.port}/@test`,
to: ["https://www.w3.org/ns/activitystreams#Public"],
cc: [],
published: "2021-01-01T00:00:00.000Z",
object: {
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/notes/1",
type: "Note",
content: "Hello, world!",
summary: null,
inReplyTo: null,
published: "2021-01-01T00:00:00.000Z",
},
}),
}
);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe("application/json");
const activity = await RawActivity.createQueryBuilder("activity")
// id is part of the jsonb column 'data'
.where("activity.data->>'id' = :id", {
id: "https://example.com/notes/1/activity",
})
.leftJoinAndSelect("activity.objects", "objects")
.getOne();
expect(activity).not.toBeUndefined();
expect(activity?.data).toEqual({
"@context": "https://www.w3.org/ns/activitystreams",
type: "Create",
id: "https://example.com/notes/1/activity",
actor: `${config.http.base_url}:${config.http.port}/@test`,
to: ["https://www.w3.org/ns/activitystreams#Public"],
cc: [],
published: "2021-01-01T00:00:00.000Z",
});
expect(activity?.objects).toHaveLength(1);
expect(activity?.objects[0].data).toEqual({
"@context": "https://www.w3.org/ns/activitystreams",
id: "https://example.com/notes/1",
type: "Note",
content: "Hello, world!",
summary: null,
inReplyTo: null,
published: "2021-01-01T00:00:00.000Z",
});
});
});
afterAll(async () => {
// Clean up user
const user = await User.findOneBy({
username: "test",
});
// Clean up tokens
const tokens = await Token.findBy({
user: {
username: "test",
},
});
const activities = await RawActivity.createQueryBuilder("activity")
.where("activity.data->>'actor' = :actor", {
actor: `${config.http.base_url}:${config.http.port}/@test`,
})
.leftJoinAndSelect("activity.objects", "objects")
.getMany();
// Delete all created objects and activities as part of testing
await Promise.all(
activities.map(async activity => {
await Promise.all(
activity.objects.map(async object => await object.remove())
);
await activity.remove();
})
);
await Promise.all(tokens.map(async token => await token.remove()));
if (user) await user.remove();
});