diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 53b2823..b63ef27 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -21,7 +21,4 @@ jobs: run: npm run lint - name: Run tests - env: - MEDIA_ENDPOINT_URL: http://localhost:3334/ - ROOT_URL: http://localhost:4444/ run: npm test diff --git a/app.arc b/app.arc index eacbc27..8c0a959 100644 --- a/app.arc +++ b/app.arc @@ -45,6 +45,9 @@ logs expires TTL contacts nickname *String +hashtag-replacements + hashtag *String + replacement String @indexes posts diff --git a/package.json b/package.json index 56abeab..9668dbb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Personal website Micropub server", "scripts": { "start": "PORT=3333 arc sandbox", - "test": "NODE_ENV=testing PORT=3334 tape test/*-test.js", + "test": "NODE_ENV=testing PORT=3334 MEDIA_ENDPOINT_URL=http://localhost:3334/ ROOT_URL=http://localhost:4444/ tape test/*-test.js", "lint": "standard" }, "dependencies": { diff --git a/src/http/get-micropub/hashtag-replacement.js b/src/http/get-micropub/hashtag-replacement.js new file mode 100644 index 0000000..c22d78d --- /dev/null +++ b/src/http/get-micropub/hashtag-replacement.js @@ -0,0 +1,37 @@ +const arc = require('@architect/functions') + +async function toResponse (items) { + const inner = items.reduce(function (result, item, _) { + result[item.hashtag] = item.replacement + return result + }, {}) + + return { + replacements: inner + } +} + +async function hashtagReplacements (filter) { + const data = await arc.tables() + const table = data['hashtag-replacements'] + if (filter) { + const results = await table.scan({ + ScanFilter: { + hashtag: { + ComparisonOperator: 'BEGINS_WITH', + AttributeValueList: [ + `#${filter}` + ] + } + } + }) + return await toResponse(results.Items) + } else { + const results = await table.scan({}) + return await toResponse(results.Items) + } +} + +module.exports = { + hashtagReplacements +} diff --git a/src/http/get-micropub/index.js b/src/http/get-micropub/index.js index 43d616e..470a5e6 100644 --- a/src/http/get-micropub/index.js +++ b/src/http/get-micropub/index.js @@ -6,6 +6,7 @@ const config = require('./config') const query = require('./query') const { setWebmentions } = require('./webmentions') const { setContexts } = require('./contexts') +const { hashtagReplacements } = require('./hashtag-replacement') async function getPost (params, scopes) { const url = params.url.replace(process.env.ROOT_URL, '') @@ -103,6 +104,8 @@ exports.handler = async function http (req) { return await { 'post-types': config.postTypes } case 'contact': return await config.contact(params.filter) + case 'hashtag-replacement': + return await hashtagReplacements(params.filter) } } return { diff --git a/test/get-micropub-test.js b/test/get-micropub-test.js index 3b6a34b..0640654 100644 --- a/test/get-micropub-test.js +++ b/test/get-micropub-test.js @@ -5,6 +5,29 @@ const fetch = require('node-fetch') const { isValidURL } = require('../src/shared/utils') const micropubUrl = 'http://localhost:3334/micropub' +async function addReplacements () { + const data = await arc.tables() + data['hashtag-replacements'].put({ + hashtag: '#IndieWeb', + replacement: 'indieweb' + }) + data['hashtag-replacements'].put({ + hashtag: '#IndieAuth', + replacement: 'indieauth' + }) + data['hashtag-replacements'].put({ + hashtag: '#TechNott', + replacement: 'tech-nottingham' + }) +} + +async function removeReplacements () { + const data = await arc.tables() + await data['hashtag-replacements'].delete({ hashtag: '#IndieWeb' }) + await data['hashtag-replacements'].delete({ hashtag: '#IndieAuth' }) + await data['hashtag-replacements'].delete({ hashtag: '#TechNott' }) +} + test('start', async t => { t.plan(1) const result = await sandbox.start() @@ -82,6 +105,87 @@ test('q=post-types returns a valid response', async t => { t.ok(body['post-types'].length > 0, 'post-types is not empty') }) +test('q=hashtag-replacement returns HTTP 200', async t => { + const response = await fetch(`${micropubUrl}?q=hashtag-replacement`) + t.ok(response.status === 200, 'hashtag-replacement should be present') +}) + +test('q=hashtag-replacement returns JSON', async t => { + const response = await fetch(`${micropubUrl}?q=hashtag-replacement`) + t.ok(response.headers.get('content-type') === 'application/json', 'hashtag-replacement should be UTF-8 JSON') +}) + +test('q=hashtag-replacement returns empty object by default', async t => { + const response = await fetch(`${micropubUrl}?q=hashtag-replacement`) + const body = await response.json() + t.ok(Object.keys(body.replacements).length === 0, 'hashtag-replacement should not have any values') +}) + +test('q=hashtag-replacement returns replacements when present', async t => { + await addReplacements() + + const response = await fetch(`${micropubUrl}?q=hashtag-replacement`) + const body = await response.json() + + const expected = { + replacements: { + '#IndieAuth': 'indieauth', + '#IndieWeb': 'indieweb', + '#TechNott': 'tech-nottingham' + } + } + + t.deepEqual(body, expected) + + t.teardown(async function () { + await removeReplacements() + }) +}) + +test('q=hashtag-replacement returns empty replacements when filtered, but not present', async t => { + const response = await fetch(`${micropubUrl}?q=hashtag-replacement&filter=indie`) + const body = await response.json() + + const expected = { + replacements: {} + } + + t.deepEqual(body, expected) +}) + +test('q=hashtag-replacement returns filtered replacements when filtered and present', async t => { + await addReplacements() + + const response = await fetch(`${micropubUrl}?q=hashtag-replacement&filter=Indie`) + const body = await response.json() + + const expected = { + replacements: { + '#IndieAuth': 'indieauth', + '#IndieWeb': 'indieweb' + } + } + + t.deepEqual(body, expected) + + t.teardown(async function () { + await removeReplacements() + }) +}) + +test('q=hashtag-replacement filtering is case sensitive', async t => { + await addReplacements() + + const response = await fetch(`${micropubUrl}?q=hashtag-replacement&filter=INDIE`) + const body = await response.json() + + t.ok(Object.keys(body.replacements).length === 0, 'hashtag-replacement should not have any values') + + t.teardown(async function () { + await removeReplacements() + }) +}) + test('end', async t => { t.plan(1) const result = await sandbox.end()