process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import rndstr from 'rndstr'; import { Test } from '@nestjs/testing'; import { jest } from '@jest/globals'; import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { GlobalModule } from '@/GlobalModule.js'; import { CoreModule } from '@/core/CoreModule.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { IActor } from '@/core/activitypub/type.js'; import { MockResolver } from '../misc/mock-resolver.js'; import { Note } from '@/models/index.js'; const host = 'https://host1.test'; function createRandomActor(): IActor & { id: string } { const preferredUsername = `${rndstr('A-Z', 4)}${rndstr('a-z', 4)}`; const actorId = `${host}/users/${preferredUsername.toLowerCase()}`; return { '@context': 'https://www.w3.org/ns/activitystreams', id: actorId, type: 'Person', preferredUsername, inbox: `${actorId}/inbox`, outbox: `${actorId}/outbox`, }; } describe('ActivityPub', () => { let noteService: ApNoteService; let personService: ApPersonService; let rendererService: ApRendererService; let resolver: MockResolver; beforeEach(async () => { const app = await Test.createTestingModule({ imports: [GlobalModule, CoreModule], }).compile(); await app.init(); app.enableShutdownHooks(); noteService = app.get<ApNoteService>(ApNoteService); personService = app.get<ApPersonService>(ApPersonService); rendererService = app.get<ApRendererService>(ApRendererService); resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService)); // Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error const federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService); jest.spyOn(federatedInstanceService, 'fetch').mockImplementation(() => new Promise(() => {})); }); describe('Parse minimum object', () => { const actor = createRandomActor(); const post = { '@context': 'https://www.w3.org/ns/activitystreams', id: `${host}/users/${rndstr('0-9a-z', 8)}`, type: 'Note', attributedTo: actor.id, to: 'https://www.w3.org/ns/activitystreams#Public', content: 'あ', }; test('Minimum Actor', async () => { resolver._register(actor.id, actor); const user = await personService.createPerson(actor.id, resolver); assert.deepStrictEqual(user.uri, actor.id); assert.deepStrictEqual(user.username, actor.preferredUsername); assert.deepStrictEqual(user.inbox, actor.inbox); }); test('Minimum Note', async () => { resolver._register(actor.id, actor); resolver._register(post.id, post); const note = await noteService.createNote(post.id, resolver, true); assert.deepStrictEqual(note?.uri, post.id); assert.deepStrictEqual(note.visibility, 'public'); assert.deepStrictEqual(note.text, post.content); }); }); describe('Name field', () => { test('Truncate long name', async () => { const actor = { ...createRandomActor(), name: rndstr('0-9a-z', 129), }; resolver._register(actor.id, actor); const user = await personService.createPerson(actor.id, resolver); assert.deepStrictEqual(user.name, actor.name.slice(0, 128)); }); test('Normalize empty name', async () => { const actor = { ...createRandomActor(), name: '', }; resolver._register(actor.id, actor); const user = await personService.createPerson(actor.id, resolver); assert.strictEqual(user.name, null); }); }); describe('Renderer', () => { test('Render an announce with visibility: followers', () => { rendererService.renderAnnounce(null, { createdAt: new Date(0), visibility: 'followers', } as Note); }); }); });