openvibers/openvibe.extensions Commits
Coupons Helper: a clear message when you report on a code you submitted
Changes
@@@ -31,6 +31,7 @@if (code === 'auth.required') return 'Connect the extension in the settings to report.';if (code === 'report.rate_limited' || code === 'lookup.rate_limited' || (err && err.status === 429)) return 'Too many requests right now. Try again in a minute.';if (code === 'coupon.not_active') return 'This code is no longer in active results.';++ if (code === 'report.own_submission') return 'You can’t report on a code you submitted.';if (code === 'network.timeout' || code === 'network.error') return 'Could not reach OpenVibe.Coupons.';return 'Something went wrong. Try again later.';}@@@ -21,6 +21,7 @@ const COUPON = {const GOOD_TOKEN = `cpx_${'a'.repeat(43)}`;const REVOKED_TOKEN = `cpx_${'r'.repeat(43)}`;const NO_REPORT_TOKEN = `cpx_${'n'.repeat(43)}`;++const SUBMITTER_TOKEN = `cpx_${'s'.repeat(43)}`; // the person who submitted COUPON/** A stub of the Coupons API with the behaviour the extension relies on. */function startStub({ delayMs = 0 } = {}) {@@@ -37,7 +38,7 @@ function startStub({ delayMs = 0 } = {}) {}, delayMs);const auth = req.headers.authorization || '';if (auth === `Bearer ${REVOKED_TOKEN}`) return send(401, { code: 'token.revoked', detail: 'disconnected' });-- if (auth && ![`Bearer ${GOOD_TOKEN}`, `Bearer ${NO_REPORT_TOKEN}`].includes(auth)) return send(401, { code: 'token.invalid', detail: 'unknown' });++ if (auth && ![`Bearer ${GOOD_TOKEN}`, `Bearer ${NO_REPORT_TOKEN}`, `Bearer ${SUBMITTER_TOKEN}`].includes(auth)) return send(401, { code: 'token.invalid', detail: 'unknown' });const u = new URL(req.url, 'http://x');if (req.method === 'GET' && u.pathname === '/api/v1/merchants/resolve') {const host = u.searchParams.get('host');@@@ -50,6 +51,7 @@ function startStub({ delayMs = 0 } = {}) {if (req.method === 'POST' && u.pathname === `/api/v1/coupons/${COUPON.id}/report`) {if (!auth) return send(401, { code: 'auth.required' });if (auth === `Bearer ${NO_REPORT_TOKEN}`) return send(403, { code: 'token.scope' });++ if (auth === `Bearer ${SUBMITTER_TOKEN}`) return send(403, { code: 'report.own_submission', detail: 'you submitted this code; its status comes from other people\'s reports' });return send(201, { accepted: true, deduplicated: false, corrected: false, coupon: { ...COUPON, status: 'reported_working', confidence: 0.67 } });}return send(404, { code: 'route.not_found' });@@@ -88,4 +90,4 @@ function fakeExt({ url = null, token = null, hasPermission = true } = {}) {};}--module.exports = { check, done, startStub, recordingFetch, fakeExt, MERCHANT, COUPON, GOOD_TOKEN, REVOKED_TOKEN, NO_REPORT_TOKEN };++module.exports = { check, done, startStub, recordingFetch, fakeExt, MERCHANT, COUPON, GOOD_TOKEN, REVOKED_TOKEN, NO_REPORT_TOKEN, SUBMITTER_TOKEN };nnew file mode 100644@@@ -0,0 +1,45 @@++'use strict';++/**++ * What the popup says when a report is refused: each refusal the Coupons API can give a report has++ * its own clear message, never the generic "Something went wrong".++ */++const assert = require('assert');++const host = require('../browser/lib/host');++const { createApiClient } = require('../browser/lib/api');++const { createPlatform } = require('../browser/lib/platform');++const { createController, messageFor } = require('../browser/lib/controller');++const config = require('../browser/lib/config');++const { check, done, startStub, fakeExt, COUPON, GOOD_TOKEN, NO_REPORT_TOKEN, SUBMITTER_TOKEN } = require('./helpers/harness');++++const GENERIC = messageFor(null);++++(async () => {++ const stub = await startStub();++ const popup = (token) => {++ const platform = createPlatform(fakeExt({ url: 'https://acme-outdoor.com/', token }), { ...config, API_ORIGIN: stub.origin });++ const api = createApiClient({ origin: stub.origin, fetch, getToken: platform.storage.getToken });++ return createController({ api, platform, host });++ };++++ await check('reporting on a code you submitted (403 report.own_submission) says so', async () => {++ const r = await popup(SUBMITTER_TOKEN).report(COUPON.id, 'worked');++ assert.strictEqual(r.ok, false);++ assert.strictEqual(r.code, 'report.own_submission');++ assert.strictEqual(r.message, 'You can’t report on a code you submitted.');++ assert.notStrictEqual(r.message, GENERIC);++ });++++ await check('the other report refusals keep their own messages; a successful report is thanked', async () => {++ const scope = await popup(NO_REPORT_TOKEN).report(COUPON.id, 'worked');++ assert.strictEqual(scope.code, 'token.scope');++ assert.notStrictEqual(scope.message, GENERIC);++ const ok = await popup(GOOD_TOKEN).report(COUPON.id, 'worked');++ assert.strictEqual(ok.ok, true);++ for (const code of ['report.own_submission', 'report.rate_limited', 'coupon.not_active', 'auth.required', 'token.revoked']) {++ assert.notStrictEqual(messageFor({ code }), GENERIC, code);++ }++ });++++ await stub.close();++ done();++})();
@@ -31,6 +31,7 @@
@@ -31,6 +31,7 @@
if (code === 'auth.required') return 'Connect the extension in the settings to report.';
if (code === 'auth.required') return 'Connect the extension in the settings to report.';
if (code === 'report.rate_limited' || code === 'lookup.rate_limited' || (err && err.status === 429)) return 'Too many requests right now. Try again in a minute.';
if (code === 'report.rate_limited' || code === 'lookup.rate_limited' || (err && err.status === 429)) return 'Too many requests right now. Try again in a minute.';
if (code === 'coupon.not_active') return 'This code is no longer in active results.';
if (code === 'coupon.not_active') return 'This code is no longer in active results.';
+ if (code === 'report.own_submission') return 'You can’t report on a code you submitted.';
if (code === 'network.timeout' || code === 'network.error') return 'Could not reach OpenVibe.Coupons.';
if (code === 'network.timeout' || code === 'network.error') return 'Could not reach OpenVibe.Coupons.';
return 'Something went wrong. Try again later.';
return 'Something went wrong. Try again later.';
}
}
@@ -21,6 +21,7 @@ const COUPON = {
@@ -21,6 +21,7 @@ const COUPON = {
const GOOD_TOKEN = `cpx_${'a'.repeat(43)}`;
const GOOD_TOKEN = `cpx_${'a'.repeat(43)}`;
const REVOKED_TOKEN = `cpx_${'r'.repeat(43)}`;
const REVOKED_TOKEN = `cpx_${'r'.repeat(43)}`;
const NO_REPORT_TOKEN = `cpx_${'n'.repeat(43)}`;
const NO_REPORT_TOKEN = `cpx_${'n'.repeat(43)}`;
+const SUBMITTER_TOKEN = `cpx_${'s'.repeat(43)}`; // the person who submitted COUPON
/** A stub of the Coupons API with the behaviour the extension relies on. */
/** A stub of the Coupons API with the behaviour the extension relies on. */
function startStub({ delayMs = 0 } = {}) {
function startStub({ delayMs = 0 } = {}) {
@@ -37,7 +38,7 @@ function startStub({ delayMs = 0 } = {}) {
@@ -37,7 +38,7 @@ function startStub({ delayMs = 0 } = {}) {
}, delayMs);
}, delayMs);
const auth = req.headers.authorization || '';
const auth = req.headers.authorization || '';
if (auth === `Bearer ${REVOKED_TOKEN}`) return send(401, { code: 'token.revoked', detail: 'disconnected' });
if (auth === `Bearer ${REVOKED_TOKEN}`) return send(401, { code: 'token.revoked', detail: 'disconnected' });
- if (auth && ![`Bearer ${GOOD_TOKEN}`, `Bearer ${NO_REPORT_TOKEN}`].includes(auth)) return send(401, { code: 'token.invalid', detail: 'unknown' });
+ if (auth && ![`Bearer ${GOOD_TOKEN}`, `Bearer ${NO_REPORT_TOKEN}`, `Bearer ${SUBMITTER_TOKEN}`].includes(auth)) return send(401, { code: 'token.invalid', detail: 'unknown' });
const u = new URL(req.url, 'http://x');
const u = new URL(req.url, 'http://x');
if (req.method === 'GET' && u.pathname === '/api/v1/merchants/resolve') {
if (req.method === 'GET' && u.pathname === '/api/v1/merchants/resolve') {
const host = u.searchParams.get('host');
const host = u.searchParams.get('host');
@@ -50,6 +51,7 @@ function startStub({ delayMs = 0 } = {}) {
@@ -50,6 +51,7 @@ function startStub({ delayMs = 0 } = {}) {
if (req.method === 'POST' && u.pathname === `/api/v1/coupons/${COUPON.id}/report`) {
if (req.method === 'POST' && u.pathname === `/api/v1/coupons/${COUPON.id}/report`) {
if (!auth) return send(401, { code: 'auth.required' });
if (!auth) return send(401, { code: 'auth.required' });
if (auth === `Bearer ${NO_REPORT_TOKEN}`) return send(403, { code: 'token.scope' });
if (auth === `Bearer ${NO_REPORT_TOKEN}`) return send(403, { code: 'token.scope' });
+ if (auth === `Bearer ${SUBMITTER_TOKEN}`) return send(403, { code: 'report.own_submission', detail: 'you submitted this code; its status comes from other people\'s reports' });
return send(201, { accepted: true, deduplicated: false, corrected: false, coupon: { ...COUPON, status: 'reported_working', confidence: 0.67 } });
return send(201, { accepted: true, deduplicated: false, corrected: false, coupon: { ...COUPON, status: 'reported_working', confidence: 0.67 } });
}
}
return send(404, { code: 'route.not_found' });
return send(404, { code: 'route.not_found' });
@@ -88,4 +90,4 @@ function fakeExt({ url = null, token = null, hasPermission = true } = {}) {
@@ -88,4 +90,4 @@ function fakeExt({ url = null, token = null, hasPermission = true } = {}) {
};
};
}
}
-module.exports = { check, done, startStub, recordingFetch, fakeExt, MERCHANT, COUPON, GOOD_TOKEN, REVOKED_TOKEN, NO_REPORT_TOKEN };
+module.exports = { check, done, startStub, recordingFetch, fakeExt, MERCHANT, COUPON, GOOD_TOKEN, REVOKED_TOKEN, NO_REPORT_TOKEN, SUBMITTER_TOKEN };
new file mode 100644
new file mode 100644
@@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
+'use strict';
+/**
+ * What the popup says when a report is refused: each refusal the Coupons API can give a report has
+ * its own clear message, never the generic "Something went wrong".
+ */
+const assert = require('assert');
+const host = require('../browser/lib/host');
+const { createApiClient } = require('../browser/lib/api');
+const { createPlatform } = require('../browser/lib/platform');
+const { createController, messageFor } = require('../browser/lib/controller');
+const config = require('../browser/lib/config');
+const { check, done, startStub, fakeExt, COUPON, GOOD_TOKEN, NO_REPORT_TOKEN, SUBMITTER_TOKEN } = require('./helpers/harness');
+
+const GENERIC = messageFor(null);
+
+(async () => {
+ const stub = await startStub();
+ const popup = (token) => {
+ const platform = createPlatform(fakeExt({ url: 'https://acme-outdoor.com/', token }), { ...config, API_ORIGIN: stub.origin });
+ const api = createApiClient({ origin: stub.origin, fetch, getToken: platform.storage.getToken });
+ return createController({ api, platform, host });
+ };
+
+ await check('reporting on a code you submitted (403 report.own_submission) says so', async () => {
+ const r = await popup(SUBMITTER_TOKEN).report(COUPON.id, 'worked');
+ assert.strictEqual(r.ok, false);
+ assert.strictEqual(r.code, 'report.own_submission');
+ assert.strictEqual(r.message, 'You can’t report on a code you submitted.');
+ assert.notStrictEqual(r.message, GENERIC);
+ });
+
+ await check('the other report refusals keep their own messages; a successful report is thanked', async () => {
+ const scope = await popup(NO_REPORT_TOKEN).report(COUPON.id, 'worked');
+ assert.strictEqual(scope.code, 'token.scope');
+ assert.notStrictEqual(scope.message, GENERIC);
+ const ok = await popup(GOOD_TOKEN).report(COUPON.id, 'worked');
+ assert.strictEqual(ok.ok, true);
+ for (const code of ['report.own_submission', 'report.rate_limited', 'coupon.not_active', 'auth.required', 'token.revoked']) {
+ assert.notStrictEqual(messageFor({ code }), GENERIC, code);
+ }
+ });
+
+ await stub.close();
+ done();
+})();