619 lines
25 KiB
JavaScript
619 lines
25 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawn } from 'node:child_process';
|
|
|
|
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dashcaddy-license-test-'));
|
|
process.env.DATA_DIR = dataDir;
|
|
process.env.DASHCADDY_WEBSITE_URL = 'https://dashcaddy.net';
|
|
process.env.DASHCADDY_LICENSE_SECRET = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
|
|
delete process.env.ADMIN_TOKEN;
|
|
fs.writeFileSync(path.join(dataDir, 'db.json'), JSON.stringify({
|
|
customers: { cus_legacy: { id: 'cus_legacy', email: 'legacy@example.com', checkoutSessionId: 'cs_legacy_123' } },
|
|
subscriptions: {},
|
|
licenses: {
|
|
lic_legacy: {
|
|
id: 'lic_legacy',
|
|
key: 'DC-AAAAA-BBBBB-CCCCC-DDDDD-EEEEE',
|
|
customerId: 'cus_legacy',
|
|
customerEmail: 'legacy@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'paid',
|
|
active: true,
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
},
|
|
},
|
|
}, null, 2));
|
|
|
|
const { app, getInvoiceSubscriptionId, getSubscriptionPeriodEnd, deliverAndTrackLicenseEmail } = await import('../src/server.js');
|
|
const store = await import('../src/store.js');
|
|
const licenseLogic = await import('../src/licenseLogic.js');
|
|
const { verifyCompatibleLicenseCode } = await import('../src/licenseCode.js');
|
|
const server = app.listen(0, '127.0.0.1');
|
|
await new Promise((resolve) => server.once('listening', resolve));
|
|
const base = `http://127.0.0.1:${server.address().port}`;
|
|
|
|
test.after(() => {
|
|
server.close();
|
|
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('legacy JSON entitlements migrate into transactional SQLite', () => {
|
|
const migrated = store.findCheckoutResult('cs_legacy_123');
|
|
assert.equal(migrated.license.key, 'DC-AAAAA-BBBBB-CCCCC-DDDDD-EEEEE');
|
|
assert.equal(fs.existsSync(path.join(dataDir, 'db.json.migrated-backup')), true);
|
|
});
|
|
|
|
test('browser preflight permits dashcaddy.net and rejects unrelated origins', async () => {
|
|
const allowed = await fetch(`${base}/api/checkout/one-time`, {
|
|
method: 'OPTIONS',
|
|
headers: {
|
|
Origin: 'https://dashcaddy.net',
|
|
'Access-Control-Request-Method': 'POST',
|
|
'Access-Control-Request-Headers': 'content-type',
|
|
},
|
|
});
|
|
assert.equal(allowed.status, 204);
|
|
assert.equal(allowed.headers.get('access-control-allow-origin'), 'https://dashcaddy.net');
|
|
|
|
const denied = await fetch(`${base}/api/checkout/one-time`, {
|
|
method: 'OPTIONS',
|
|
headers: { Origin: 'https://evil.example', 'Access-Control-Request-Method': 'POST' },
|
|
});
|
|
assert.equal(denied.status, 403);
|
|
assert.equal(denied.headers.get('access-control-allow-origin'), null);
|
|
});
|
|
|
|
test('checkout rejects invalid email before contacting Stripe', async () => {
|
|
const response = await fetch(`${base}/api/checkout/one-time`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Origin: 'https://dashcaddy.net' },
|
|
body: JSON.stringify({ planCode: 'premium_30d', customerEmail: 'not-an-email' }),
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.deepEqual(await response.json(), { ok: false, error: 'Valid customerEmail is required' });
|
|
});
|
|
|
|
test('admin store is closed by default', async () => {
|
|
const response = await fetch(`${base}/api/admin/debug/store`);
|
|
assert.equal(response.status, 404);
|
|
});
|
|
|
|
test('checkout lookup returns the exact stored key accepted by validation', async () => {
|
|
store.upsertCustomer({ id: 'cus_test_lookup', email: 'buyer@example.com', checkoutSessionId: 'cs_test_lookup_123' });
|
|
const license = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_test_lookup',
|
|
customerId: 'cus_test_lookup',
|
|
customerEmail: 'buyer@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
store.updateLicense(license.id, { emailDeliveryStatus: 'delivered', emailDeliveryVia: 'smtp' });
|
|
|
|
const lookup = await fetch(`${base}/api/checkout/session/cs_test_lookup_123`, {
|
|
headers: { Origin: 'https://dashcaddy.net' },
|
|
});
|
|
assert.equal(lookup.status, 200);
|
|
const result = await lookup.json();
|
|
assert.equal(result.status, 'delivered');
|
|
assert.equal(result.code, license.key);
|
|
assert.match(result.code, /^DC-(?:[0-9A-Z]{5}-){4}[0-9A-Z]{5}$/);
|
|
const offline = verifyCompatibleLicenseCode(result.code);
|
|
assert.equal(offline.valid, true);
|
|
assert.equal(offline.durationDays, 30);
|
|
assert.equal(result.productId, 'pro-30d');
|
|
|
|
const validate = await fetch(`${base}/api/license/validate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ code: result.code, machineId: 'product-machine-1' }),
|
|
});
|
|
assert.equal(validate.status, 200);
|
|
const validation = await validate.json();
|
|
assert.equal(validation.success, true);
|
|
assert.equal(validation.expiresAt, license.expiresAt);
|
|
assert.ok(Array.isArray(validation.features));
|
|
assert.ok(validation.features.includes('sso'));
|
|
|
|
const wrongMachine = await fetch(`${base}/api/license/validate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ code: result.code, machineId: 'product-machine-2' }),
|
|
});
|
|
assert.equal(wrongMachine.status, 400);
|
|
assert.match((await wrongMachine.json()).error, /another machine/);
|
|
});
|
|
|
|
test('license activation rejects missing machine identity', async () => {
|
|
const license = store.createOrUpdateLicenseBySubscription('sub_missing_machine', {
|
|
subscriptionId: 'sub_missing_machine',
|
|
customerId: 'cus_missing_machine',
|
|
customerEmail: 'machine@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
const response = await fetch(`${base}/api/license/validate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ code: license.key }),
|
|
});
|
|
assert.equal(response.status, 400);
|
|
assert.match((await response.json()).error, /Machine identity is required/);
|
|
});
|
|
|
|
test('duplicate Stripe payment intent never extends a one-time license twice', () => {
|
|
const first = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_idempotency_1',
|
|
customerId: 'cus_idempotency',
|
|
customerEmail: 'idempotency@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
const firstExpiry = first.expiresAt;
|
|
|
|
const duplicate = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_idempotency_1',
|
|
customerId: 'cus_idempotency',
|
|
customerEmail: 'idempotency@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
assert.equal(duplicate.idempotent, true);
|
|
assert.equal(duplicate.expiresAt, firstExpiry);
|
|
|
|
const secondPayment = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_idempotency_2',
|
|
customerId: 'cus_idempotency',
|
|
customerEmail: 'idempotency@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
assert.equal(secondPayment.extended, true);
|
|
assert.ok(new Date(secondPayment.expiresAt).getTime() > new Date(firstExpiry).getTime());
|
|
});
|
|
|
|
test('concurrent duplicate payment delivery is atomic across processes', async () => {
|
|
const script = `
|
|
import { grantOneTimeLicense } from './src/licenseLogic.js';
|
|
grantOneTimeLicense({
|
|
paymentIntentId: 'pi_concurrent_same',
|
|
customerId: 'cus_concurrent_same',
|
|
customerEmail: 'concurrent@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30
|
|
});
|
|
`;
|
|
const run = () => new Promise((resolve, reject) => {
|
|
const child = spawn(process.execPath, ['--input-type=module', '-e', script], {
|
|
cwd: path.resolve(import.meta.dirname, '..'),
|
|
env: { ...process.env, DATA_DIR: dataDir },
|
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
});
|
|
let stderr = '';
|
|
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
child.on('exit', code => code === 0 ? resolve() : reject(new Error(stderr || `child exited ${code}`)));
|
|
});
|
|
await Promise.all([run(), run()]);
|
|
|
|
const license = store.findLicenseByCustomerId('cus_concurrent_same');
|
|
assert.deepEqual(license.processedPaymentIntentIds, ['pi_concurrent_same']);
|
|
const days = (new Date(license.expiresAt).getTime() - Date.now()) / 86400000;
|
|
assert.ok(days > 29 && days < 31);
|
|
});
|
|
|
|
test('checkout creation is rate limited per client IP', async () => {
|
|
let last;
|
|
for (let i = 0; i < 21; i++) {
|
|
last = await fetch(`${base}/api/checkout/one-time`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Forwarded-For': '203.0.113.55',
|
|
},
|
|
body: JSON.stringify({ planCode: 'invalid-rate-test', customerEmail: 'buyer@example.com' }),
|
|
});
|
|
}
|
|
assert.equal(last.status, 429);
|
|
assert.ok(Number(last.headers.get('retry-after')) > 0);
|
|
});
|
|
|
|
test('webhook event claim is durable and single-use', () => {
|
|
assert.equal(store.claimWebhookEvent('evt_test_durable_1').claimed, true);
|
|
assert.equal(store.claimWebhookEvent('evt_test_durable_1').status, 'processing');
|
|
store.finishWebhookEvent('evt_test_durable_1');
|
|
assert.equal(store.claimWebhookEvent('evt_test_durable_1').status, 'completed');
|
|
});
|
|
|
|
test('pinned Stripe API fixtures resolve subscription and paid-through period', () => {
|
|
const invoice = {
|
|
id: 'in_fixture_1',
|
|
parent: { subscription_details: { subscription: { id: 'sub_fixture_1' } } },
|
|
};
|
|
const subscription = {
|
|
id: 'sub_fixture_1',
|
|
items: { data: [{ current_period_end: 1789990000 }, { current_period_end: 1790000000 }] },
|
|
};
|
|
assert.equal(getInvoiceSubscriptionId(invoice), 'sub_fixture_1');
|
|
assert.equal(getSubscriptionPeriodEnd(subscription), new Date(1790000000 * 1000).toISOString());
|
|
});
|
|
|
|
test('invoice-level claim prevents distinct events from renewing one invoice twice', () => {
|
|
assert.equal(store.claimBusinessObject('invoice.paid', 'in_same_invoice').claimed, true);
|
|
assert.equal(store.claimBusinessObject('invoice.paid', 'in_same_invoice').status, 'processing');
|
|
store.finishBusinessObject('invoice.paid', 'in_same_invoice');
|
|
assert.equal(store.claimBusinessObject('invoice.paid', 'in_same_invoice').status, 'completed');
|
|
});
|
|
|
|
test('subscription cancellation keeps access only until existing expiry', () => {
|
|
const license = store.createOrUpdateLicenseBySubscription('sub_cancel_test', {
|
|
subscriptionId: 'sub_cancel_test',
|
|
customerId: 'cus_cancel_test',
|
|
customerEmail: 'cancel@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
const canceled = store.cancelLicenseBySubscription('sub_cancel_test');
|
|
assert.equal(canceled.id, license.id);
|
|
assert.equal(canceled.status, 'canceled');
|
|
assert.equal(canceled.active, true);
|
|
});
|
|
|
|
test('subscription cancellation fails closed when paid-through expiry is absent', () => {
|
|
store.createOrUpdateLicenseBySubscription('sub_cancel_no_expiry', {
|
|
subscriptionId: 'sub_cancel_no_expiry',
|
|
customerId: 'cus_cancel_no_expiry',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: null,
|
|
active: true,
|
|
});
|
|
assert.equal(store.cancelLicenseBySubscription('sub_cancel_no_expiry').active, false);
|
|
});
|
|
|
|
test('checkout sessions remain independently retrievable for repeat customers', () => {
|
|
store.upsertCustomer({ id: 'cus_repeat', email: 'repeat@example.com', checkoutSessionId: 'cs_repeat_first' });
|
|
store.upsertCustomer({ id: 'cus_repeat', email: 'repeat@example.com', checkoutSessionId: 'cs_repeat_second' });
|
|
store.createOrUpdateLicenseBySubscription('sub_repeat', {
|
|
subscriptionId: 'sub_repeat',
|
|
customerId: 'cus_repeat',
|
|
customerEmail: 'repeat@example.com',
|
|
planCode: 'premium_90d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
assert.ok(store.findCheckoutResult('cs_repeat_first')?.license);
|
|
assert.ok(store.findCheckoutResult('cs_repeat_second')?.license);
|
|
});
|
|
|
|
test('expired payment grace blocks license validation', () => {
|
|
const license = store.createOrUpdateLicenseBySubscription('sub_grace_expired', {
|
|
subscriptionId: 'sub_grace_expired',
|
|
customerId: 'cus_grace_expired',
|
|
customerEmail: 'grace@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
store.markLicensePaymentFailed('sub_grace_expired', new Date(Date.now() - 1000).toISOString());
|
|
const result = licenseLogic.validateLicense({ code: license.key, machine: { hostname: 'grace-host' } });
|
|
assert.equal(result.success, false);
|
|
assert.match(result.message, /grace period has expired/);
|
|
});
|
|
|
|
test('past-due subscription updates preserve an existing grace deadline', () => {
|
|
const graceUntil = new Date(Date.now() + 3 * 86400000).toISOString();
|
|
store.createOrUpdateLicenseBySubscription('sub_grace_order', {
|
|
subscriptionId: 'sub_grace_order',
|
|
customerId: 'cus_grace_order',
|
|
customerEmail: 'grace-order@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
store.markLicensePaymentFailed('sub_grace_order', graceUntil);
|
|
const updated = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_grace_order',
|
|
customerId: 'cus_grace_order',
|
|
customerEmail: 'grace-order@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'past_due',
|
|
currentPeriodEnd: new Date(Date.now() + 86400000).toISOString(),
|
|
});
|
|
assert.equal(updated.graceUntil, graceUntil);
|
|
});
|
|
|
|
test('newer failure event cannot shorten an existing grace deadline', () => {
|
|
const laterGrace = new Date(Date.now() + 7 * 86400000).toISOString();
|
|
const earlierGrace = new Date(Date.now() + 2 * 86400000).toISOString();
|
|
store.createOrUpdateLicenseBySubscription('sub_grace_monotonic', {
|
|
subscriptionId: 'sub_grace_monotonic',
|
|
customerId: 'cus_grace_monotonic',
|
|
customerEmail: 'grace-monotonic@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
store.markLicensePaymentFailed('sub_grace_monotonic', laterGrace, 100, 'evt_failure_1');
|
|
const second = store.markLicensePaymentFailed('sub_grace_monotonic', earlierGrace, 101, 'evt_failure_2');
|
|
assert.equal(second.graceUntil, laterGrace);
|
|
});
|
|
|
|
test('stale failure cannot regress a newer paid entitlement', () => {
|
|
const paidExpiry = new Date(Date.now() + 30 * 86400000).toISOString();
|
|
const paid = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_monotonic_paid',
|
|
customerId: 'cus_monotonic_paid',
|
|
customerEmail: 'monotonic@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
currentPeriodEnd: paidExpiry,
|
|
eventCreated: 200,
|
|
});
|
|
const staleFailure = store.markLicensePaymentFailed(
|
|
'sub_monotonic_paid',
|
|
new Date(Date.now() + 7 * 86400000).toISOString(),
|
|
100,
|
|
);
|
|
assert.equal(staleFailure.staleEventIgnored, true);
|
|
assert.equal(store.findLicenseByCustomerId('cus_monotonic_paid').status, 'active');
|
|
assert.equal(store.findLicenseByCustomerId('cus_monotonic_paid').expiresAt, paid.expiresAt);
|
|
});
|
|
|
|
test('older subscription update cannot replace newer expiry or status', () => {
|
|
const newerExpiry = new Date(Date.now() + 90 * 86400000).toISOString();
|
|
licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_monotonic_update',
|
|
customerId: 'cus_monotonic_update',
|
|
customerEmail: 'update-order@example.com',
|
|
planCode: 'premium_90d',
|
|
status: 'active',
|
|
currentPeriodEnd: newerExpiry,
|
|
eventCreated: 500,
|
|
});
|
|
const stale = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_monotonic_update',
|
|
customerId: 'cus_monotonic_update',
|
|
customerEmail: 'update-order@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'past_due',
|
|
currentPeriodEnd: new Date(Date.now() + 10 * 86400000).toISOString(),
|
|
eventCreated: 400,
|
|
});
|
|
assert.equal(stale.staleEventIgnored, true);
|
|
const current = store.findLicenseByCustomerId('cus_monotonic_update');
|
|
assert.equal(current.status, 'active');
|
|
assert.equal(current.expiresAt, newerExpiry);
|
|
});
|
|
|
|
test('idempotent entitlement can resume incomplete email fulfillment', () => {
|
|
const first = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_resume_email',
|
|
customerId: 'cus_resume_email',
|
|
customerEmail: 'resume@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
store.updateLicense(first.id, { emailDeliveryStatus: 'pending' });
|
|
const retry = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_resume_email',
|
|
customerId: 'cus_resume_email',
|
|
customerEmail: 'resume@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
assert.equal(retry.idempotent, true);
|
|
assert.equal(retry.emailDeliveryStatus, 'pending');
|
|
assert.equal(retry.expiresAt, first.expiresAt);
|
|
});
|
|
|
|
test('equal-timestamp event tie-breaker cannot regress paid state', () => {
|
|
const expiry = new Date(Date.now() + 30 * 86400000).toISOString();
|
|
licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_equal_timestamp',
|
|
customerId: 'cus_equal_timestamp',
|
|
customerEmail: 'equal@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
currentPeriodEnd: expiry,
|
|
eventCreated: 900,
|
|
eventId: 'evt_z_newer_tie',
|
|
});
|
|
const staleFailure = store.markLicensePaymentFailed(
|
|
'sub_equal_timestamp',
|
|
new Date(Date.now() + 7 * 86400000).toISOString(),
|
|
900,
|
|
'evt_a_older_tie',
|
|
);
|
|
assert.equal(staleFailure.staleEventIgnored, true);
|
|
assert.equal(store.findLicenseByCustomerId('cus_equal_timestamp').status, 'active');
|
|
});
|
|
|
|
test('future payment grace keeps access after the paid period expires', () => {
|
|
const license = store.createOrUpdateLicenseBySubscription('sub_grace_active', {
|
|
subscriptionId: 'sub_grace_active',
|
|
customerId: 'cus_grace_active',
|
|
customerEmail: 'grace-active@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'past_due',
|
|
expiresAt: new Date(Date.now() - 1000).toISOString(),
|
|
graceUntil: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
});
|
|
const result = licenseLogic.validateLicense({ code: license.key, machine: { hostname: 'grace-active-host' } });
|
|
assert.equal(result.success, true);
|
|
});
|
|
|
|
test('subscription retry preserves recorded email delivery and stable key', () => {
|
|
const first = store.createOrUpdateLicenseBySubscription('sub_email_delivered', {
|
|
subscriptionId: 'sub_email_delivered',
|
|
customerId: 'cus_email_delivered',
|
|
customerEmail: 'delivered@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
|
active: true,
|
|
emailDeliveryStatus: 'delivered',
|
|
emailDeliveredAt: new Date().toISOString(),
|
|
lastStripeEventCreated: 100,
|
|
lastStripeEventId: 'evt_email_first',
|
|
});
|
|
const retry = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_email_delivered',
|
|
customerId: 'cus_email_delivered',
|
|
customerEmail: 'delivered@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
currentPeriodEnd: new Date(Date.now() + 2 * 86400000).toISOString(),
|
|
eventCreated: 101,
|
|
eventId: 'evt_email_retry',
|
|
});
|
|
assert.equal(retry.key, first.key);
|
|
assert.equal(retry.emailDeliveryStatus, 'delivered');
|
|
});
|
|
|
|
test('subscription failure and cancellation cannot revoke one-time paid-through time', () => {
|
|
const oneTime = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_mixed_mode',
|
|
customerId: 'cus_mixed_mode',
|
|
customerEmail: 'mixed@example.com',
|
|
planCode: 'premium_90d',
|
|
durationDays: 90,
|
|
});
|
|
const subscription = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_mixed_mode',
|
|
customerId: 'cus_mixed_mode',
|
|
customerEmail: 'mixed@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
currentPeriodEnd: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
eventCreated: 1000,
|
|
eventId: 'evt_mixed_active',
|
|
});
|
|
assert.equal(subscription.key, oneTime.key);
|
|
assert.equal(subscription.oneTimeExpiresAt, oneTime.oneTimeExpiresAt);
|
|
|
|
const failed = store.markLicensePaymentFailed(
|
|
'sub_mixed_mode',
|
|
new Date(Date.now() - 1000).toISOString(),
|
|
1001,
|
|
'evt_mixed_failed',
|
|
);
|
|
assert.equal(failed.subscriptionStatus, 'past_due');
|
|
assert.equal(failed.status, 'active');
|
|
assert.equal(licenseLogic.validateLicense({ code: failed.key, machine: { hostname: 'mixed-host' } }).success, true);
|
|
|
|
const canceled = store.cancelLicenseBySubscription('sub_mixed_mode', 1002, 'evt_mixed_canceled');
|
|
assert.equal(canceled.subscriptionStatus, 'canceled');
|
|
assert.equal(canceled.active, true);
|
|
assert.equal(canceled.key, oneTime.key);
|
|
});
|
|
|
|
test('renewal email delivery is tracked per Stripe invoice', async () => {
|
|
const license = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_invoice_email',
|
|
customerId: 'cus_invoice_email',
|
|
customerEmail: 'invoice-email@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
currentPeriodEnd: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
eventCreated: 2000,
|
|
eventId: 'evt_invoice_email',
|
|
});
|
|
let sent = 0;
|
|
const sender = async () => { sent += 1; return { delivered: true, via: 'test' }; };
|
|
const args = {
|
|
customerEmail: 'invoice-email@example.com',
|
|
code: license.key,
|
|
durationDays: 30,
|
|
planCode: 'premium_30d',
|
|
extended: true,
|
|
};
|
|
|
|
await deliverAndTrackLicenseEmail({ ...args, license, deliveryId: 'in_invoice_1' }, sender);
|
|
let fresh = store.findLicenseByCustomerId('cus_invoice_email');
|
|
await deliverAndTrackLicenseEmail({ ...args, license: fresh, deliveryId: 'in_invoice_1' }, sender);
|
|
assert.equal(sent, 1);
|
|
fresh = store.findLicenseByCustomerId('cus_invoice_email');
|
|
await deliverAndTrackLicenseEmail({ ...args, license: fresh, deliveryId: 'in_invoice_2' }, sender);
|
|
assert.equal(sent, 2);
|
|
assert.equal(store.findLicenseByCustomerId('cus_invoice_email').lastEmailDeliveryId, 'in_invoice_2');
|
|
});
|
|
|
|
test('matching email never transfers a license across Stripe customer IDs', () => {
|
|
const first = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_owner_a',
|
|
customerId: 'cus_owner_a',
|
|
customerEmail: 'shared@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
const second = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_owner_b',
|
|
customerId: 'cus_owner_b',
|
|
customerEmail: 'shared@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
assert.notEqual(second.key, first.key);
|
|
assert.equal(store.findLicenseByCustomerId('cus_owner_a').key, first.key);
|
|
assert.equal(store.findLicenseByCustomerId('cus_owner_b').key, second.key);
|
|
|
|
store.upsertCustomer({ id: 'cus_owner_a', email: 'shared@example.com', checkoutSessionId: 'cs_owner_a' });
|
|
store.upsertCustomer({ id: 'cus_owner_b', email: 'shared@example.com', checkoutSessionId: 'cs_owner_b' });
|
|
assert.equal(store.findCheckoutResult('cs_owner_a').license.key, first.key);
|
|
assert.equal(store.findCheckoutResult('cs_owner_b').license.key, second.key);
|
|
|
|
const secondSubscription = licenseLogic.syncLicenseFromSubscription({
|
|
subscriptionId: 'sub_owner_b',
|
|
customerId: 'cus_owner_b',
|
|
customerEmail: 'shared@example.com',
|
|
planCode: 'premium_30d',
|
|
status: 'active',
|
|
currentPeriodEnd: new Date(Date.now() + 30 * 86400000).toISOString(),
|
|
eventCreated: 3000,
|
|
eventId: 'evt_owner_b',
|
|
});
|
|
assert.equal(secondSubscription.key, second.key);
|
|
assert.notEqual(secondSubscription.key, first.key);
|
|
});
|
|
|
|
test('concurrent first activation allows exactly one machine', async () => {
|
|
const license = licenseLogic.grantOneTimeLicense({
|
|
paymentIntentId: 'pi_machine_race',
|
|
customerId: 'cus_machine_race',
|
|
customerEmail: 'machine-race@example.com',
|
|
planCode: 'premium_30d',
|
|
durationDays: 30,
|
|
});
|
|
const script = `
|
|
import { validateLicense } from './src/licenseLogic.js';
|
|
const result = validateLicense({ code: process.env.TEST_LICENSE_CODE, machine: { hostname: process.env.TEST_MACHINE } });
|
|
process.stdout.write(JSON.stringify(result));
|
|
`;
|
|
const run = (machine) => new Promise((resolve, reject) => {
|
|
const child = spawn(process.execPath, ['--input-type=module', '-e', script], {
|
|
cwd: path.resolve('.'),
|
|
env: { ...process.env, DATA_DIR: dataDir, TEST_LICENSE_CODE: license.key, TEST_MACHINE: machine },
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
let stdout = '';
|
|
let stderr = '';
|
|
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
child.on('exit', (code) => code === 0 ? resolve(JSON.parse(stdout)) : reject(new Error(stderr)));
|
|
});
|
|
const results = await Promise.all([run('machine-race-a'), run('machine-race-b')]);
|
|
assert.equal(results.filter((item) => item.success).length, 1);
|
|
assert.equal(results.filter((item) => !item.success).length, 1);
|
|
});
|