From 178c98c17fbc14af6b002d082fc29b46121eed3b Mon Sep 17 00:00:00 2001 From: Fabien 'egg' O'Carroll Date: Tue, 30 Jul 2024 17:33:25 +0700 Subject: [PATCH] Fixed handling of single item collections (#20688) Fedify will not use an array for the `items` key of collections when there is only a single item, which wasn't being handled in our activitypub api module. Now we always return an array so that the components recieve consistent data. --- .../src/api/activitypub.test.ts | 123 +++++++++++++++++- .../src/api/activitypub.ts | 6 +- 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/apps/admin-x-activitypub/src/api/activitypub.test.ts b/apps/admin-x-activitypub/src/api/activitypub.test.ts index 3e742b65f4..e194474e2c 100644 --- a/apps/admin-x-activitypub/src/api/activitypub.test.ts +++ b/apps/admin-x-activitypub/src/api/activitypub.test.ts @@ -95,7 +95,7 @@ describe('ActivityPubAPI', function () { expect(actual).toEqual(expected); }); - test('Returns an the items array when the inbox is not empty', async function () { + test('Returns all the items array when the inbox is not empty', async function () { const fakeFetch = Fetch({ 'https://auth.api/': { response: JSONResponse({ @@ -137,6 +137,49 @@ describe('ActivityPubAPI', function () { 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/inbox/index': { + response: + JSONResponse({ + type: 'Collection', + items: { + 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 () { @@ -199,7 +242,7 @@ describe('ActivityPubAPI', function () { expect(actual).toEqual(expected); }); - test('Returns an the items array when the following is not empty', async function () { + test('Returns all the items array when the following is not empty', async function () { const fakeFetch = Fetch({ 'https://auth.api/': { response: JSONResponse({ @@ -235,6 +278,43 @@ describe('ActivityPubAPI', function () { 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 () { @@ -297,7 +377,7 @@ describe('ActivityPubAPI', function () { expect(actual).toEqual(expected); }); - test('Returns an the items array when the followers is not empty', async function () { + test('Returns all the items array when the followers is not empty', async function () { const fakeFetch = Fetch({ 'https://auth.api/': { response: JSONResponse({ @@ -333,6 +413,43 @@ describe('ActivityPubAPI', function () { 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/followers/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.getFollowers(); + const expected: Activity[] = [ + { + type: 'Person' + } + ]; + + expect(actual).toEqual(expected); + }); }); describe('follow', function () { diff --git a/apps/admin-x-activitypub/src/api/activitypub.ts b/apps/admin-x-activitypub/src/api/activitypub.ts index fb7a185131..edb0da0ac1 100644 --- a/apps/admin-x-activitypub/src/api/activitypub.ts +++ b/apps/admin-x-activitypub/src/api/activitypub.ts @@ -45,7 +45,7 @@ export class ActivityPubAPI { return []; } if ('items' in json) { - return Array.isArray(json?.items) ? json.items : []; + return Array.isArray(json.items) ? json.items : [json.items]; } return []; } @@ -60,7 +60,7 @@ export class ActivityPubAPI { return []; } if ('items' in json) { - return Array.isArray(json?.items) ? json.items : []; + return Array.isArray(json.items) ? json.items : [json.items]; } return []; } @@ -86,7 +86,7 @@ export class ActivityPubAPI { return []; } if ('items' in json) { - return Array.isArray(json?.items) ? json.items : []; + return Array.isArray(json.items) ? json.items : [json.items]; } return []; }