mirror of
https://github.com/lawrencehook/remove-youtube-suggestions.git
synced 2026-07-25 06:54:31 +00:00
8825666512
Add RYS Premium subscription system Introduce optional paid tier with sign-in, Stripe billing, and premium feature gating. Core features remain free. Includes Node.js server for auth, payments, and license management. Extension changes: - Sign-in flow with magic link email - Premium/upgrade/account modals with branded UI - Premium feature gating (60+ advanced settings) - Password lock and scheduling (premium) - Fix "hide all but first row" for YouTube's new flat homepage DOM - Hide sidebar ad panels by default - Enable "Hide all Shorts" by default Server: - Magic link auth with rate limiting - Stripe checkout, webhooks, and billing portal - JWT-based license tokens - AWS SES transactional emails (welcome, sign-in, cancellation) - Grandfathered donor support Also adds CI workflow, server test suite, and extension unit tests.
155 lines
4.9 KiB
JavaScript
155 lines
4.9 KiB
JavaScript
const { describe, it, beforeEach } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { loadSourceFiles, resetStorage, setStorageData } = require('../setup');
|
|
|
|
// Helper to create a mock JWT token
|
|
function createMockJWT(payload, expiresInSeconds = 3600) {
|
|
const header = { alg: 'HS256', typ: 'JWT' };
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const fullPayload = {
|
|
...payload,
|
|
iat: now,
|
|
exp: now + expiresInSeconds,
|
|
};
|
|
|
|
const encode = (obj) => Buffer.from(JSON.stringify(obj)).toString('base64url');
|
|
return `${encode(header)}.${encode(fullPayload)}.fake-signature`;
|
|
}
|
|
|
|
describe('License Billing Methods', () => {
|
|
let context;
|
|
let fetchCalls;
|
|
let fetchResponse;
|
|
|
|
beforeEach(() => {
|
|
resetStorage();
|
|
fetchCalls = [];
|
|
fetchResponse = { ok: true, status: 200, json: async () => ({}) };
|
|
|
|
context = loadSourceFiles(['shared/config.js', 'shared/auth.js', 'shared/license.js'], {
|
|
fetch: async (url, options) => {
|
|
fetchCalls.push({ url, options });
|
|
return fetchResponse;
|
|
},
|
|
});
|
|
});
|
|
|
|
describe('createCheckoutSession', () => {
|
|
it('should throw when not signed in', async () => {
|
|
await assert.rejects(
|
|
context.License.createCheckoutSession('monthly'),
|
|
/Not signed in/
|
|
);
|
|
});
|
|
|
|
it('should call checkout endpoint with correct parameters', async () => {
|
|
setStorageData({ session_token: 'valid-session' });
|
|
fetchResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ checkout_url: 'https://checkout.stripe.com/test' }),
|
|
};
|
|
|
|
const result = await context.License.createCheckoutSession('monthly');
|
|
|
|
assert.strictEqual(result, 'https://checkout.stripe.com/test');
|
|
assert.strictEqual(fetchCalls.length, 1);
|
|
assert.ok(fetchCalls[0].url.includes('/checkout/create'));
|
|
assert.strictEqual(fetchCalls[0].options.method, 'POST');
|
|
assert.strictEqual(fetchCalls[0].options.headers['Content-Type'], 'application/json');
|
|
assert.ok(fetchCalls[0].options.headers['Authorization'].includes('Bearer valid-session'));
|
|
|
|
const body = JSON.parse(fetchCalls[0].options.body);
|
|
assert.strictEqual(body.plan, 'monthly');
|
|
});
|
|
|
|
it('should throw and sign out on 401', async () => {
|
|
setStorageData({ session_token: 'expired-session' });
|
|
fetchResponse = { ok: false, status: 401 };
|
|
|
|
await assert.rejects(
|
|
context.License.createCheckoutSession('yearly'),
|
|
/Session expired/
|
|
);
|
|
});
|
|
|
|
it('should throw on server error with message', async () => {
|
|
setStorageData({ session_token: 'valid-session' });
|
|
fetchResponse = {
|
|
ok: false,
|
|
status: 500,
|
|
json: async () => ({ error: 'Payment provider unavailable' }),
|
|
};
|
|
|
|
await assert.rejects(
|
|
context.License.createCheckoutSession('monthly'),
|
|
/Payment provider unavailable/
|
|
);
|
|
});
|
|
|
|
it('should throw generic error when response has no error message', async () => {
|
|
setStorageData({ session_token: 'valid-session' });
|
|
fetchResponse = {
|
|
ok: false,
|
|
status: 500,
|
|
json: async () => { throw new Error('Invalid JSON'); },
|
|
};
|
|
|
|
await assert.rejects(
|
|
context.License.createCheckoutSession('monthly'),
|
|
/Failed to create checkout session/
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('createBillingPortalSession', () => {
|
|
it('should throw when not signed in', async () => {
|
|
await assert.rejects(
|
|
context.License.createBillingPortalSession(),
|
|
/Not signed in/
|
|
);
|
|
});
|
|
|
|
it('should call billing portal endpoint correctly', async () => {
|
|
setStorageData({ session_token: 'valid-session' });
|
|
fetchResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ url: 'https://billing.stripe.com/portal' }),
|
|
};
|
|
|
|
const result = await context.License.createBillingPortalSession();
|
|
|
|
assert.strictEqual(result, 'https://billing.stripe.com/portal');
|
|
assert.strictEqual(fetchCalls.length, 1);
|
|
assert.ok(fetchCalls[0].url.includes('/billing/portal'));
|
|
assert.strictEqual(fetchCalls[0].options.method, 'POST');
|
|
assert.ok(fetchCalls[0].options.headers['Authorization'].includes('Bearer valid-session'));
|
|
});
|
|
|
|
it('should throw and sign out on 401', async () => {
|
|
setStorageData({ session_token: 'expired-session' });
|
|
fetchResponse = { ok: false, status: 401 };
|
|
|
|
await assert.rejects(
|
|
context.License.createBillingPortalSession(),
|
|
/Session expired/
|
|
);
|
|
});
|
|
|
|
it('should throw on server error', async () => {
|
|
setStorageData({ session_token: 'valid-session' });
|
|
fetchResponse = {
|
|
ok: false,
|
|
status: 500,
|
|
json: async () => ({ error: 'Customer not found' }),
|
|
};
|
|
|
|
await assert.rejects(
|
|
context.License.createBillingPortalSession(),
|
|
/Customer not found/
|
|
);
|
|
});
|
|
});
|
|
});
|