Implemented and tested ResourceService as separate class

refs https://github.com/TryGhost/Team/issues/2465

This code is still in the Ghost package for now as it's essentially glue code.
This commit is contained in:
Fabien "egg" O'Carroll 2023-01-23 15:46:53 +07:00 committed by Fabien 'egg' O'Carroll
parent f746f223cd
commit 182e0b831d
3 changed files with 179 additions and 17 deletions

View File

@ -0,0 +1,45 @@
const ObjectID = require('bson-objectid').default;
/**
* @typedef {import('@tryghost/webmentions/lib/MentionsAPI').IResourceService} IResourceService
*/
/**
* @implements {IResourceService}
*/
module.exports = class ResourceService {
/** @type {import('@tryghost/url-utils/lib/url-utils')} */
#urlUtils;
/** @type {import('../url')} */
#urlService;
/**
* @param {object} deps
* @param {import('@tryghost/url-utils/lib/url-utils')} deps.urlUtils
* @param {import('../url')} deps.urlService
*/
constructor(deps) {
this.#urlUtils = deps.urlUtils;
this.#urlService = deps.urlService;
}
/**
* @param {URL} url
* @returns {Promise<import('@tryghost/webmentions/lib/MentionsAPI').ResourceResult>}
*/
async getByURL(url) {
const path = this.#urlUtils.absoluteToRelative(url.href, {withoutSubdirectory: true});
const resource = this.#urlService.getResource(path);
if (resource?.config?.type === 'posts') {
return {
type: 'post',
id: ObjectID.createFromHexString(resource.data.id)
};
}
return {
type: null,
id: null
};
}
};

View File

@ -1,4 +1,3 @@
const ObjectID = require('bson-objectid').default;
const MentionController = require('./MentionController'); const MentionController = require('./MentionController');
const WebmentionMetadata = require('./WebmentionMetadata'); const WebmentionMetadata = require('./WebmentionMetadata');
const { const {
@ -7,6 +6,7 @@ const {
MentionDiscoveryService MentionDiscoveryService
} = require('@tryghost/webmentions'); } = require('@tryghost/webmentions');
const BookshelfMentionRepository = require('./BookshelfMentionRepository'); const BookshelfMentionRepository = require('./BookshelfMentionRepository');
const ResourceService = require('./ResourceService');
const RoutingService = require('./RoutingService'); const RoutingService = require('./RoutingService');
const models = require('../../models'); const models = require('../../models');
@ -30,6 +30,10 @@ module.exports = {
}); });
const webmentionMetadata = new WebmentionMetadata(); const webmentionMetadata = new WebmentionMetadata();
const discoveryService = new MentionDiscoveryService({externalRequest}); const discoveryService = new MentionDiscoveryService({externalRequest});
const resourceService = new ResourceService({
urlUtils,
urlService
});
const routingService = new RoutingService({ const routingService = new RoutingService({
siteUrl: new URL(urlUtils.getSiteUrl()) siteUrl: new URL(urlUtils.getSiteUrl())
}); });
@ -37,22 +41,7 @@ module.exports = {
const api = new MentionsAPI({ const api = new MentionsAPI({
repository, repository,
webmentionMetadata, webmentionMetadata,
resourceService: { resourceService,
async getByURL(url) {
const path = urlUtils.absoluteToRelative(url.href, {withoutSubdirectory: true});
const resource = urlService.getResource(path);
if (resource?.config?.type === 'posts') {
return {
type: 'post',
id: ObjectID.createFromHexString(resource.data.id)
};
}
return {
type: null,
id: null
};
}
},
routingService routingService
}); });

View File

@ -0,0 +1,128 @@
const assert = require('assert');
const sinon = require('sinon');
const ResourceService = require('../../../../../core/server/services/mentions/ResourceService');
const UrlUtils = require('@tryghost/url-utils');
const UrlService = require('../../../../../core/server/services/url/UrlService');
function stubGetResource(urlService) {
const getResource = sinon.stub(urlService, 'getResource');
getResource.withArgs('/post-resource').returns({
config: {
type: 'posts'
},
data: {
id: '63ce473f992390b739b00b01'
}
});
getResource.withArgs('/tag-resource').returns({
config: {
type: 'tags'
},
data: {
id: '63ce473f992390b739b00b02'
}
});
getResource.withArgs('/no-resource').returns(null);
return getResource;
}
describe('ResourceService', function () {
describe('getByURL', function () {
it('Correctly converts post resources', async function () {
const urlUtils = new UrlUtils({
getSiteUrl() {
return 'https://site.com/blah/';
},
getSubdir() {
return '/blah';
},
getAdminUrl() {
return 'https://admin.com';
}
});
const urlService = new UrlService();
const resourceService = new ResourceService({
urlUtils,
urlService
});
const getResource = stubGetResource(urlService);
const result = await resourceService.getByURL(
new URL('https://site.com/blah/post-resource')
);
assert(getResource.calledWithExactly('/post-resource'));
assert.equal(result.type, 'post');
assert.equal(result.id.toHexString(), '63ce473f992390b739b00b01');
});
it('Does not convert tag resources', async function () {
const urlUtils = new UrlUtils({
getSiteUrl() {
return 'https://site.com/blah/';
},
getSubdir() {
return '/blah';
},
getAdminUrl() {
return 'https://admin.com';
}
});
const urlService = new UrlService();
const resourceService = new ResourceService({
urlUtils,
urlService
});
const getResource = stubGetResource(urlService);
const result = await resourceService.getByURL(
new URL('https://site.com/blah/tag-resource')
);
assert(getResource.calledWithExactly('/tag-resource'));
assert.equal(result.type, null);
assert.equal(result.id, null);
});
it('Handles non-resources', async function () {
const urlUtils = new UrlUtils({
getSiteUrl() {
return 'https://site.com/blah/';
},
getSubdir() {
return '/blah';
},
getAdminUrl() {
return 'https://admin.com';
}
});
const urlService = new UrlService();
const resourceService = new ResourceService({
urlUtils,
urlService
});
const getResource = stubGetResource(urlService);
const result = await resourceService.getByURL(
new URL('https://site.com/blah/no-resource')
);
assert(getResource.calledWithExactly('/no-resource'));
assert.equal(result.type, null);
assert.equal(result.id, null);
});
});
});