diff --git a/services/f-droid/f-droid.service.js b/services/f-droid/f-droid.service.js index bc2d36717d1fe..f7748dba1a94f 100644 --- a/services/f-droid/f-droid.service.js +++ b/services/f-droid/f-droid.service.js @@ -16,6 +16,8 @@ const schema = Joi.object({ }).required() const queryParamSchema = Joi.object({ + serverFqdn: Joi.string().hostname(), + endpoint: Joi.string(), include_prereleases: Joi.equal(''), }).required() @@ -26,13 +28,25 @@ export default class FDroid extends BaseJsonService { '/f-droid/v/{appId}': { get: { summary: 'F-Droid Version', - description: - '[F-Droid](https://f-droid.org/) is a catalogue of Open Source Android apps', + description: ` + [F-Droid](https://f-droid.org/) is a catalogue of Open Source Android apps. + + This badge by default uses f-droid.org, but also supports custom repos. + `, parameters: [ pathParam({ name: 'appId', example: 'org.dystopia.email', }), + queryParam({ + name: 'serverFqdn', + example: 'apt.izzysoft.de', + }), + queryParam({ + name: 'endpoint', + example: 'fdroid', + description: `If the API is not located at root path, specify the additional path to the API.`, + }), queryParam({ name: 'include_prereleases', schema: { type: 'boolean' }, @@ -45,8 +59,12 @@ export default class FDroid extends BaseJsonService { static defaultBadgeData = { label: 'f-droid' } - async fetch({ appId }) { - const url = `https://f-droid.org/api/v1/packages/${appId}` + async fetch({ serverFqdn, endpoint, appId }) { + endpoint = endpoint.replace('^/|/$', '') + if (endpoint !== '') { + endpoint = `/${endpoint}` + } + const url = `https://${serverFqdn}${endpoint}/api/v1/packages/${appId}` return this._requestJson({ schema, url, @@ -71,8 +89,15 @@ export default class FDroid extends BaseJsonService { return { version } } - async handle({ appId }, { include_prereleases: includePre }) { - const json = await this.fetch({ appId }) + async handle( + { appId }, + { + serverFqdn = 'f-droid.org', + endpoint = '', + include_prereleases: includePre, + }, + ) { + const json = await this.fetch({ serverFqdn, endpoint, appId }) const suggested = includePre === undefined const { version } = this.transform({ json, suggested }) return renderVersionBadge({ version }) diff --git a/services/f-droid/f-droid.tester.js b/services/f-droid/f-droid.tester.js index 5dde646939987..436f2a7379584 100644 --- a/services/f-droid/f-droid.tester.js +++ b/services/f-droid/f-droid.tester.js @@ -31,26 +31,33 @@ const testJson = ` const base = 'https://f-droid.org/api/v1' const path = `/packages/${testPkg}` -t.create('Package is found') +t.create('f-droid.org: Package is found') .get(`/v/${testPkg}.json`) .intercept(nock => nock(base).get(path).reply(200, testJson)) .expectBadge({ label: 'f-droid', message: 'v0.2.7' }) -t.create('Package is found (pre-release)') +t.create('f-droid.org: Package is found (pre-release)') .get(`/v/${testPkg}.json?include_prereleases`) .intercept(nock => nock(base).get(path).reply(200, testJson)) .expectBadge({ label: 'f-droid', message: 'v0.2.11' }) -t.create('Package is not found with 403') +t.create('f-droid.org: Package is not found with 403') .get(`/v/${testPkg}.json`) .intercept(nock => nock(base).get(path).reply(403, 'some 403 text')) .expectBadge({ label: 'f-droid', message: 'app not found' }) -t.create('Package is not found with 404') +t.create('f-droid.org: Package is not found with 404') .get('/v/io.shiels.does.not.exist.json') + .intercept(nock => + nock(base) + .get('/packages/io.shiels.does.not.exist') + .reply(404, 'some 404 text'), + ) .expectBadge({ label: 'f-droid', message: 'app not found' }) -t.create('Package is not found with no packages available (empty array)"') +t.create( + 'f-droid.org: Package is not found with no packages available (empty array)"', +) .get(`/v/${testPkg}.json`) .intercept(nock => nock(base) @@ -59,7 +66,9 @@ t.create('Package is not found with no packages available (empty array)"') ) .expectBadge({ label: 'f-droid', message: 'no packages found' }) -t.create('Package is not found with no packages available (missing array)"') +t.create( + 'f-droid.org: Package is not found with no packages available (missing array)"', +) .get(`/v/${testPkg}.json`) .intercept(nock => nock(base).get(path).reply(200, `{"packageName":"${testPkg}"}`), @@ -67,9 +76,68 @@ t.create('Package is not found with no packages available (missing array)"') .expectBadge({ label: 'f-droid', message: 'no packages found' }) /* If this test fails, either the API has changed or the app was deleted. */ -t.create('The real api did not change') +t.create('f-droid.org: The real api did not change') .get('/v/org.thosp.yourlocalweather.json') .expectBadge({ label: 'f-droid', message: isVPlusDottedVersionAtLeastOne, }) + +const base2 = 'https://apt.izzysoft.de/fdroid/api/v1' +const path2 = `/packages/${testPkg}` + +t.create('custom repo: Package is found') + .get(`/v/${testPkg}.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid`) + .intercept(nock => nock(base2).get(path2).reply(200, testJson)) + .expectBadge({ label: 'f-droid', message: 'v0.2.7' }) + +t.create('custom repo: Package is found (pre-release)') + .get( + `/v/${testPkg}.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid&include_prereleases`, + ) + .intercept(nock => nock(base2).get(path2).reply(200, testJson)) + .expectBadge({ label: 'f-droid', message: 'v0.2.11' }) + +t.create('custom repo: Package is not found with 403') + .get(`/v/${testPkg}.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid`) + .intercept(nock => nock(base2).get(path2).reply(403, 'some 403 text')) + .expectBadge({ label: 'f-droid', message: 'app not found' }) + +t.create('custom repo: Package is not found with 404') + .get( + '/v/io.shiels.does.not.exist.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid', + ) + .intercept(nock => + nock(base2) + .get('/packages/io.shiels.does.not.exist') + .reply(404, 'some 404 text'), + ) + .expectBadge({ label: 'f-droid', message: 'app not found' }) + +t.create( + 'custom repo: Package is not found with no packages available (empty array)"', +) + .get(`/v/${testPkg}.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid`) + .intercept(nock => + nock(base2) + .get(path2) + .reply(200, `{"packageName":"${testPkg}","packages":[]}`), + ) + .expectBadge({ label: 'f-droid', message: 'no packages found' }) + +t.create( + 'custom repo: Package is not found with no packages available (missing array)"', +) + .get(`/v/${testPkg}.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid`) + .intercept(nock => + nock(base2).get(path2).reply(200, `{"packageName":"${testPkg}"}`), + ) + .expectBadge({ label: 'f-droid', message: 'no packages found' }) + +/* If this test fails, either the API has changed or the app was deleted. */ +t.create('custom repo: The real api did not change') + .get('/v/com.looker.droidify.json?serverFqdn=apt.izzysoft.de&endpoint=fdroid') + .expectBadge({ + label: 'f-droid', + message: isVPlusDottedVersionAtLeastOne, + })