← back to Animals
test: verify Stripe upgrade-checkout line items + live-key guard
2ab8a9ef17685682d8fae2047eb343b6d1001d82 · 2026-05-26 12:27:52 -0700 · SteveStudio2
Files touched
A test/lib/stripe.test.js
Diff
commit 2ab8a9ef17685682d8fae2047eb343b6d1001d82
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 26 12:27:52 2026 -0700
test: verify Stripe upgrade-checkout line items + live-key guard
---
test/lib/stripe.test.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 108 insertions(+)
diff --git a/test/lib/stripe.test.js b/test/lib/stripe.test.js
new file mode 100644
index 0000000..d6d65fa
--- /dev/null
+++ b/test/lib/stripe.test.js
@@ -0,0 +1,108 @@
+// test/lib/stripe.test.js
+//
+// Contract tests for src/lib/stripe.js — the upgrade-order Checkout builder.
+// Verifies the money-shape of the Checkout session WITHOUT hitting Stripe:
+// - starter = $499 one-time setup + $49/mo hosting
+// - pro = $999 one-time setup + $49/mo hosting
+// - mode: subscription, correct success/cancel URLs, order metadata
+// - getStripe() refuses an sk_live_ key outside NODE_ENV=production
+//
+// Mocking strategy: set a fake sk_test_ key so getStripe() constructs a real
+// Stripe instance (the constructor makes no network call), then replace
+// stripe.checkout.sessions.create with a stub that captures its args.
+
+process.env.STRIPE_SECRET_KEY = 'sk_test_fake_for_unit_tests';
+
+import { describe, it } from 'node:test';
+import assert from 'node:assert/strict';
+
+const { getStripe, planSpec, createUpgradeCheckoutSession, isStripeConfigured } =
+ await import('../../src/lib/stripe.js');
+
+// Stub the network-bound create() on the cached singleton.
+const stripe = getStripe();
+let captured = null;
+stripe.checkout.sessions.create = async (args) => {
+ captured = args;
+ return { id: 'cs_test_123', url: 'https://checkout.stripe.com/c/pay/cs_test_123' };
+};
+
+function lineAmounts(args) {
+ return args.line_items.map((li) => ({
+ name: li.price_data.product_data.name,
+ amount: li.price_data.unit_amount,
+ recurring: li.price_data.recurring?.interval || null,
+ }));
+}
+
+describe('stripe upgrade checkout', () => {
+ it('reports configured when a key is present', () => {
+ assert.equal(isStripeConfigured(), true);
+ });
+
+ it('planSpec exposes $499 / $999 setup fees', () => {
+ assert.equal(planSpec('starter_499').setup_cents, 49900);
+ assert.equal(planSpec('pro_999').setup_cents, 99900);
+ assert.equal(planSpec('nonsense'), null);
+ });
+
+ it('starter session = $499 setup + $49/mo hosting, subscription mode', async () => {
+ const session = await createUpgradeCheckoutSession({
+ orderId: 42, plan: 'starter_499', customerEmail: 'a@b.com',
+ businessName: 'Acme Vet', baseUrl: 'https://example.test',
+ });
+ assert.equal(session.url, 'https://checkout.stripe.com/c/pay/cs_test_123');
+ assert.equal(captured.mode, 'subscription');
+ assert.equal(captured.customer_email, 'a@b.com');
+
+ const items = lineAmounts(captured);
+ assert.equal(items.length, 2);
+ // one-time setup
+ assert.equal(items[0].amount, 49900);
+ assert.equal(items[0].recurring, null);
+ assert.match(items[0].name, /Acme Vet/);
+ // recurring hosting
+ assert.equal(items[1].amount, 4900);
+ assert.equal(items[1].recurring, 'month');
+
+ assert.match(captured.success_url, /\/upgrade\/success\?session_id=\{CHECKOUT_SESSION_ID\}$/);
+ assert.match(captured.cancel_url, /\/upgrade\/cancel\?order_id=42$/);
+ assert.equal(captured.metadata.upgrade_order_id, '42');
+ assert.equal(captured.metadata.plan, 'starter_499');
+ });
+
+ it('pro session = $999 setup + $49/mo hosting', async () => {
+ await createUpgradeCheckoutSession({
+ orderId: 7, plan: 'pro_999', customerEmail: 'c@d.com',
+ businessName: null, baseUrl: 'https://example.test',
+ });
+ const items = lineAmounts(captured);
+ assert.equal(items[0].amount, 99900);
+ assert.equal(items[1].amount, 4900);
+ assert.equal(items[1].recurring, 'month');
+ });
+
+ it('rejects an unknown plan', async () => {
+ await assert.rejects(
+ () => createUpgradeCheckoutSession({ orderId: 1, plan: 'enterprise_4999', baseUrl: 'x' }),
+ /unknown plan/,
+ );
+ });
+});
+
+describe('stripe live-key guard', () => {
+ it('refuses an sk_live_ key when NODE_ENV !== production', async () => {
+ // Fresh module instance so the cached singleton from above doesn't shadow.
+ const mod = await import('../../src/lib/stripe.js?livecheck=1');
+ const prevKey = process.env.STRIPE_SECRET_KEY;
+ const prevEnv = process.env.NODE_ENV;
+ process.env.STRIPE_SECRET_KEY = 'sk_live_should_be_refused';
+ delete process.env.NODE_ENV;
+ try {
+ assert.throws(() => mod.getStripe(), /live Stripe key outside/);
+ } finally {
+ process.env.STRIPE_SECRET_KEY = prevKey;
+ if (prevEnv !== undefined) process.env.NODE_ENV = prevEnv;
+ }
+ });
+});
← ecd4ce9 Add lost-pet alert opt-in surface (/me prefs page + signup c
·
back to Animals
·
Untrack committed .log files and ignore *.pre-* backups d2da3c7 →