Skip to content

Commit

Permalink
Added reverse-chronological ordering to outbox items
Browse files Browse the repository at this point in the history
refs [AP-371](https://linear.app/tryghost/issue/AP-371/outbox-item-ordering-is-not-in-reverse-chronological-order)

Added reverse-chronological ordering to outbox items by reversing the order of
the items in the outbox before dispatching them. This could be improved by
comparing the `published` property on the object associated with the outbox
item, but for now this is a good enough.
  • Loading branch information
mike182uk committed Aug 29, 2024
1 parent 188a1d7 commit e0b15f2
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ services:
test: "mysql -ughost -ppassword activitypub -e 'select 1'"
interval: 1s
retries: 120
ports:
- "3308:3306"

wiremock:
image: wiremock/wiremock:latest
Expand Down
17 changes: 15 additions & 2 deletions features/outbox.feature
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ Feature: Outbox
And a "Announce(C)" Activity "An" by "Alice"
And a "Follow(Us)" Activity "F" by "Alice"
And a "Accept(F)" Activity "A" by "Alice"
And "Alice" adds "C" to the Outbox
And "Alice" adds "An" to the Outbox
And "Alice" adds "F" to the Outbox
And "Alice" adds "A" to the Outbox
When the contents of the outbox is requested
Then the outbox contains 1 activity
And a "Create(Article)" activity is in the Outbox
Then the outbox contains 2 activities

Scenario: outbox contains is ordered reverse chronologically
Given an Actor "Alice"
And a "Create(Article)" Activity "A1" by "Alice"
And a "Create(Article)" Activity "A2" by "Alice"
And "Alice" adds "A1" to the Outbox
And "Alice" adds "A2" to the Outbox
When the contents of the outbox is requested
Then the outbox contains 2 activities
And the items in the outbox are in the order: "A2, A1"
64 changes: 58 additions & 6 deletions features/step_definitions/stepdefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BeforeAll, AfterAll, Before, After, Given, When, Then } from '@cucumber
import { v4 as uuidv4 } from 'uuid';
import { WireMock } from 'wiremock-captain';

async function createActivity(activityType, object, actor, remote = true) {
async function createActivity(activityType, object, actor, name) {
if (activityType === 'Follow') {
return {
'@context': [
Expand Down Expand Up @@ -40,7 +40,7 @@ async function createActivity(activityType, object, actor, remote = true) {
'https://w3id.org/security/data-integrity/v1',
],
'type': 'Create',
'id': 'http://wiremock:8080/create/1',
'id': `http://wiremock:8080/create/${name || '1'}`,
'to': 'as:Public',
'object': object,
actor: actor,
Expand Down Expand Up @@ -166,8 +166,6 @@ BeforeAll(async function () {
database: process.env.MYSQL_DATABASE
}
});

await client('key_value').truncate();
});

BeforeAll(async function () {
Expand All @@ -180,6 +178,7 @@ AfterAll(async function () {

Before(async function () {
await captain.clearAllRequests();
await client('key_value').truncate();
});

Before(async function () {
Expand All @@ -206,7 +205,7 @@ Given('a {string} Activity {string} by {string}', async function (activityDef, n
const object = this.actors[objectName] ?? this.activities[objectName] ?? await createObject(objectName);
const actor = this.actors[actorName];

const activity = await createActivity(activityType, object, actor);
const activity = await createActivity(activityType, object, actor, name);

this.activities[name] = activity;
});
Expand Down Expand Up @@ -371,6 +370,59 @@ When('the contents of the outbox is requested', async function () {
this.response = await response.json();
});

Then('the outbox contains {int} activity', function (count) {
When('{string} adds {string} to the Outbox', async function (actorName, activityName) {
if (!this.actors[actorName]) {
throw new Error(`Could not find Actor ${actorName}`);
}
if (!this.activities[activityName]) {
throw new Error(`Could not find Activity ${activityName}`);
}

const activity = this.activities[activityName];

// Add activity to the db
const activityKey = JSON.stringify([activity.id]);

if (
(await client('key_value').select('value').where({ key: activityKey })).length === 0
) {
await client('key_value').insert({
key: activityKey,
value: JSON.stringify(activity)
})
}

// Add activity to the outbox
const outboxKey = JSON.stringify(["sites","activitypub-testing:8083","outbox"]);
const outbox = (
await client('key_value').select('value').where('key', outboxKey)
).map((item) => item.value)[0] || [];

outbox.push(activity.id)

if (outbox.length === 1) {
await client('key_value').insert({
key: outboxKey,
value: JSON.stringify(outbox)
})
} else {
await client('key_value')
.where({ key: outboxKey })
.update({
value: JSON.stringify(outbox)
})
}
});

Then(/^the outbox contains ([0-9]+) (activity|activities)$/, function (count, word) {
assert.equal(this.response.totalItems, count);
});

Then('the items in the outbox are in the order: {string}', function (order) {
order.split(",").map((item) => item.trim()).forEach((activityName, index) => {
assert.ok(
this.response.orderedItems[index].id.endsWith(activityName),
`${this.response.orderedItems[index].id} does not end with ${activityName} as expected`
)
})
});
2 changes: 1 addition & 1 deletion src/dispatchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ export async function outboxDispatcher(
}
}
return {
items,
items: items.reverse(),
};
}

Expand Down

0 comments on commit e0b15f2

Please sign in to comment.