TryGhost/Ghost

View on GitHub
apps/admin-x-activitypub/src/api/activitypub.test.ts

Summary

Maintainability
F
1 wk
Test Coverage
import {Activity, ActivityPubAPI} from './activitypub';

function NotFound() {
    return new Response(null, {
        status: 404
    });
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function JSONResponse(data: any, contentType = 'application/json', status = 200) {
    return new Response(JSON.stringify(data), {
        status,
        headers: {
            'Content-Type': contentType
        }
    });
}

type Spec = {
    response: Response,
    assert?: (resource: URL, init?: RequestInit) => Promise<void>
};

function Fetch(specs: Record<string, Spec>) {
    return async function (resource: URL, init?: RequestInit): Promise<Response> {
        const spec = specs[resource.href];
        if (!spec) {
            return NotFound();
        }
        if (spec.assert) {
            await spec.assert(resource, init);
        }
        return spec.response;
    };
}

describe('ActivityPubAPI', function () {
    describe('getInbox', function () {
        test('It passes the token to the inbox endpoint', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/inbox/index': {
                    async assert(_resource, init) {
                        const headers = new Headers(init?.headers);
                        expect(headers.get('Authorization')).toContain('fake-token');
                    },
                    response: JSONResponse({
                        type: 'Collection',
                        items: []
                    })
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            await api.getInbox();
        });

        test('Returns an empty array when the inbox is empty', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/inbox/index': {
                    response: JSONResponse({
                        type: 'Collection',
                        items: []
                    })
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getInbox();
            const expected: never[] = [];

            expect(actual).toEqual(expected);
        });

        test('Returns all the items array when the inbox is not empty', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/inbox/index': {
                    response:
                     JSONResponse({
                         type: 'Collection',
                         orderedItems: [{
                             type: 'Create',
                             object: {
                                 type: 'Note'
                             }
                         }]
                     })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getInbox();
            const expected: Activity[] = [
                {
                    type: 'Create',
                    object: {
                        type: 'Note'
                    }
                }
            ];

            expect(actual).toEqual(expected);
        });

        test('Returns an array when the orderedItems key is a single object', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/inbox/index': {
                    response:
                     JSONResponse({
                         type: 'Collection',
                         orderedItems: {
                             type: 'Create',
                             object: {
                                 type: 'Note'
                             }
                         }
                     })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getInbox();
            const expected: Activity[] = [
                {
                    type: 'Create',
                    object: {
                        type: 'Note'
                    }
                }
            ];

            expect(actual).toEqual(expected);
        });
    });

    describe('getFollowing', function () {
        test('It passes the token to the following endpoint', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/following/index': {
                    async assert(_resource, init) {
                        const headers = new Headers(init?.headers);
                        expect(headers.get('Authorization')).toContain('fake-token');
                    },
                    response: JSONResponse({
                        type: 'Collection',
                        items: []
                    })
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            await api.getFollowing();
        });

        test('Returns an empty array when the following is empty', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/following/index': {
                    response: JSONResponse({
                        type: 'Collection',
                        items: []
                    })
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getFollowing();
            const expected: never[] = [];

            expect(actual).toEqual(expected);
        });

        test('Returns all the items array when the following is not empty', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/following/index': {
                    response:
                     JSONResponse({
                         type: 'Collection',
                         orderedItems: [{
                             type: 'Person'
                         }]
                     })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getFollowing();
            const expected: Activity[] = [
                {
                    type: 'Person'
                }
            ];

            expect(actual).toEqual(expected);
        });

        test('Returns an array when the items key is a single object', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/following/index': {
                    response:
                     JSONResponse({
                         type: 'Collection',
                         items: {
                             type: 'Person'
                         }
                     })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getFollowing();
            const expected: Activity[] = [
                {
                    type: 'Person'
                }
            ];

            expect(actual).toEqual(expected);
        });
    });

    describe('getFollowers', function () {
        test('It passes the token to the followers endpoint', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/followers/index': {
                    async assert(_resource, init) {
                        const headers = new Headers(init?.headers);
                        expect(headers.get('Authorization')).toContain('fake-token');
                    },
                    response: JSONResponse({
                        type: 'Collection',
                        orderedItems: []
                    })
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            await api.getFollowers();
        });

        test('Returns an empty array when the followers is empty', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/followers/index': {
                    response: JSONResponse({
                        type: 'Collection',
                        orderedItems: []
                    })
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getFollowers();
            const expected: never[] = [];

            expect(actual).toEqual(expected);
        });

        test('Returns all the items array when the followers is not empty', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/followers/index': {
                    response:
                     JSONResponse({
                         type: 'Collection',
                         orderedItems: [{
                             type: 'Person'
                         }]
                     })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getFollowers();
            const expected: Activity[] = [
                {
                    type: 'Person'
                }
            ];

            expect(actual).toEqual(expected);
        });
    });

    describe('follow', function () {
        test('It passes the token to the follow endpoint', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/actions/follow/@user@domain.com': {
                    async assert(_resource, init) {
                        const headers = new Headers(init?.headers);
                        expect(headers.get('Authorization')).toContain('fake-token');
                    },
                    response: JSONResponse({})
                }
            });
            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            await api.follow('@user@domain.com');
        });
    });

    describe('getAllActivities', function () {
        test('It fetches all activities navigating pagination', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/activities/index?limit=50': {
                    response: JSONResponse({
                        items: [{type: 'Create', object: {type: 'Note'}}],
                        nextCursor: 'next-cursor'
                    })
                },
                'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&cursor=next-cursor': {
                    response: JSONResponse({
                        items: [{type: 'Announce', object: {type: 'Article'}}],
                        nextCursor: null
                    })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getAllActivities();
            const expected: Activity[] = [
                {type: 'Create', object: {type: 'Note'}},
                {type: 'Announce', object: {type: 'Article'}}
            ];

            expect(actual).toEqual(expected);
        });

        test('It fetches a user\'s own activities', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&includeOwn=true': {
                    response: JSONResponse({
                        items: [{type: 'Create', object: {type: 'Note'}}],
                        nextCursor: null
                    })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getAllActivities(true);
            const expected: Activity[] = [
                {type: 'Create', object: {type: 'Note'}}
            ];

            expect(actual).toEqual(expected);
        });

        test('It fetches activities with replies', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&includeReplies=true': {
                    response: JSONResponse({
                        items: [{type: 'Create', object: {type: 'Note'}}],
                        nextCursor: null
                    })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getAllActivities(false, true);
            const expected: Activity[] = [
                {type: 'Create', object: {type: 'Note'}}
            ];

            expect(actual).toEqual(expected);
        });

        test('It fetches filtered activities', async function () {
            const fakeFetch = Fetch({
                'https://auth.api/': {
                    response: JSONResponse({
                        identities: [{
                            token: 'fake-token'
                        }]
                    })
                },
                [`https://activitypub.api/.ghost/activitypub/activities/index?limit=50&filter=%7B%22type%22%3A%5B%22Create%3ANote%22%5D%7D`]: {
                    response: JSONResponse({
                        items: [{type: 'Create', object: {type: 'Note'}}],
                        nextCursor: null
                    })
                }
            });

            const api = new ActivityPubAPI(
                new URL('https://activitypub.api'),
                new URL('https://auth.api'),
                'index',
                fakeFetch
            );

            const actual = await api.getAllActivities(false, false, {type: ['Create:Note']});
            const expected: Activity[] = [
                {type: 'Create', object: {type: 'Note'}}
            ];

            expect(actual).toEqual(expected);
        });
    });
});