@skillsmith/core 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +16 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.events.d.ts.map +1 -1
- package/dist/src/api/client.events.js +10 -1
- package/dist/src/api/client.events.js.map +1 -1
- package/dist/src/api/client.health.d.ts.map +1 -1
- package/dist/src/api/client.health.js +7 -1
- package/dist/src/api/client.health.js.map +1 -1
- package/dist/src/api/client.js +11 -1
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/utils.d.ts +14 -3
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/api/utils.js +14 -4
- package/dist/src/api/utils.js.map +1 -1
- package/dist/src/db/createDatabase.d.ts.map +1 -1
- package/dist/src/db/createDatabase.js +18 -2
- package/dist/src/db/createDatabase.js.map +1 -1
- package/dist/src/db/drivers/betterSqlite3Driver.d.ts +7 -0
- package/dist/src/db/drivers/betterSqlite3Driver.d.ts.map +1 -1
- package/dist/src/db/drivers/betterSqlite3Driver.js +21 -1
- package/dist/src/db/drivers/betterSqlite3Driver.js.map +1 -1
- package/dist/src/db/drivers/corruption.d.ts +31 -0
- package/dist/src/db/drivers/corruption.d.ts.map +1 -0
- package/dist/src/db/drivers/corruption.js +57 -0
- package/dist/src/db/drivers/corruption.js.map +1 -0
- package/dist/src/db/drivers/sqljsDriver.d.ts.map +1 -1
- package/dist/src/db/drivers/sqljsDriver.js +48 -7
- package/dist/src/db/drivers/sqljsDriver.js.map +1 -1
- package/dist/src/exports/services.d.ts +0 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +14 -3
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/meta-list-filter.d.ts +55 -0
- package/dist/src/indexer/meta-list-filter.d.ts.map +1 -0
- package/dist/src/indexer/meta-list-filter.js +65 -0
- package/dist/src/indexer/meta-list-filter.js.map +1 -0
- package/dist/src/indexer/meta-list-filter.test.d.ts +7 -0
- package/dist/src/indexer/meta-list-filter.test.d.ts.map +1 -0
- package/dist/src/indexer/meta-list-filter.test.js +139 -0
- package/dist/src/indexer/meta-list-filter.test.js.map +1 -0
- package/dist/src/types.d.ts +12 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/tests/api/client.auth.test.js +50 -0
- package/dist/tests/api/client.auth.test.js.map +1 -1
- package/dist/tests/api/utils.test.js +7 -3
- package/dist/tests/api/utils.test.js.map +1 -1
- package/dist/tests/db/corruption.test.d.ts +11 -0
- package/dist/tests/db/corruption.test.d.ts.map +1 -0
- package/dist/tests/db/corruption.test.js +109 -0
- package/dist/tests/db/corruption.test.js.map +1 -0
- package/dist/tests/{billing/stripe-validators.test.d.ts → security/sanitization-stripe-ids.test.d.ts} +1 -1
- package/dist/tests/security/sanitization-stripe-ids.test.d.ts.map +1 -0
- package/dist/tests/{billing/stripe-validators.test.js → security/sanitization-stripe-ids.test.js} +1 -1
- package/dist/tests/security/sanitization-stripe-ids.test.js.map +1 -0
- package/dist/tests/shared.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +5 -1
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/package.json +2 -7
- package/dist/src/billing/BillingService.d.ts +0 -101
- package/dist/src/billing/BillingService.d.ts.map +0 -1
- package/dist/src/billing/BillingService.helpers.d.ts +0 -15
- package/dist/src/billing/BillingService.helpers.d.ts.map +0 -1
- package/dist/src/billing/BillingService.helpers.js +0 -45
- package/dist/src/billing/BillingService.helpers.js.map +0 -1
- package/dist/src/billing/BillingService.js +0 -263
- package/dist/src/billing/BillingService.js.map +0 -1
- package/dist/src/billing/BillingService.types.d.ts +0 -52
- package/dist/src/billing/BillingService.types.d.ts.map +0 -1
- package/dist/src/billing/BillingService.types.js +0 -6
- package/dist/src/billing/BillingService.types.js.map +0 -1
- package/dist/src/billing/GDPRComplianceService.d.ts +0 -81
- package/dist/src/billing/GDPRComplianceService.d.ts.map +0 -1
- package/dist/src/billing/GDPRComplianceService.js +0 -361
- package/dist/src/billing/GDPRComplianceService.js.map +0 -1
- package/dist/src/billing/StripeClient.d.ts +0 -119
- package/dist/src/billing/StripeClient.d.ts.map +0 -1
- package/dist/src/billing/StripeClient.js +0 -405
- package/dist/src/billing/StripeClient.js.map +0 -1
- package/dist/src/billing/StripeReconciliationJob.d.ts +0 -50
- package/dist/src/billing/StripeReconciliationJob.d.ts.map +0 -1
- package/dist/src/billing/StripeReconciliationJob.js +0 -365
- package/dist/src/billing/StripeReconciliationJob.js.map +0 -1
- package/dist/src/billing/StripeWebhookHandler.d.ts +0 -49
- package/dist/src/billing/StripeWebhookHandler.d.ts.map +0 -1
- package/dist/src/billing/StripeWebhookHandler.js +0 -162
- package/dist/src/billing/StripeWebhookHandler.js.map +0 -1
- package/dist/src/billing/gdpr-types.d.ts +0 -103
- package/dist/src/billing/gdpr-types.d.ts.map +0 -1
- package/dist/src/billing/gdpr-types.js +0 -7
- package/dist/src/billing/gdpr-types.js.map +0 -1
- package/dist/src/billing/index.d.ts +0 -18
- package/dist/src/billing/index.d.ts.map +0 -1
- package/dist/src/billing/index.js +0 -19
- package/dist/src/billing/index.js.map +0 -1
- package/dist/src/billing/reconciliation-helpers.d.ts +0 -16
- package/dist/src/billing/reconciliation-helpers.d.ts.map +0 -1
- package/dist/src/billing/reconciliation-helpers.js +0 -53
- package/dist/src/billing/reconciliation-helpers.js.map +0 -1
- package/dist/src/billing/reconciliation-types.d.ts +0 -71
- package/dist/src/billing/reconciliation-types.d.ts.map +0 -1
- package/dist/src/billing/reconciliation-types.js +0 -7
- package/dist/src/billing/reconciliation-types.js.map +0 -1
- package/dist/src/billing/stripe-client-types.d.ts +0 -45
- package/dist/src/billing/stripe-client-types.d.ts.map +0 -1
- package/dist/src/billing/stripe-client-types.js +0 -7
- package/dist/src/billing/stripe-client-types.js.map +0 -1
- package/dist/src/billing/stripe-helpers.d.ts +0 -17
- package/dist/src/billing/stripe-helpers.d.ts.map +0 -1
- package/dist/src/billing/stripe-helpers.js +0 -50
- package/dist/src/billing/stripe-helpers.js.map +0 -1
- package/dist/src/billing/types.d.ts +0 -266
- package/dist/src/billing/types.d.ts.map +0 -1
- package/dist/src/billing/types.js +0 -23
- package/dist/src/billing/types.js.map +0 -1
- package/dist/src/billing/webhook-handlers.d.ts +0 -56
- package/dist/src/billing/webhook-handlers.d.ts.map +0 -1
- package/dist/src/billing/webhook-handlers.js +0 -303
- package/dist/src/billing/webhook-handlers.js.map +0 -1
- package/dist/src/billing/webhook-types.d.ts +0 -42
- package/dist/src/billing/webhook-types.d.ts.map +0 -1
- package/dist/src/billing/webhook-types.js +0 -7
- package/dist/src/billing/webhook-types.js.map +0 -1
- package/dist/tests/billing/BillingService.test.d.ts +0 -7
- package/dist/tests/billing/BillingService.test.d.ts.map +0 -1
- package/dist/tests/billing/BillingService.test.js +0 -168
- package/dist/tests/billing/BillingService.test.js.map +0 -1
- package/dist/tests/billing/GDPRCompliance.test.d.ts +0 -7
- package/dist/tests/billing/GDPRCompliance.test.d.ts.map +0 -1
- package/dist/tests/billing/GDPRCompliance.test.js +0 -380
- package/dist/tests/billing/GDPRCompliance.test.js.map +0 -1
- package/dist/tests/billing/StripeClient.test.d.ts +0 -18
- package/dist/tests/billing/StripeClient.test.d.ts.map +0 -1
- package/dist/tests/billing/StripeClient.test.js +0 -566
- package/dist/tests/billing/StripeClient.test.js.map +0 -1
- package/dist/tests/billing/StripeReconciliation.test.d.ts +0 -7
- package/dist/tests/billing/StripeReconciliation.test.d.ts.map +0 -1
- package/dist/tests/billing/StripeReconciliation.test.js +0 -266
- package/dist/tests/billing/StripeReconciliation.test.js.map +0 -1
- package/dist/tests/billing/StripeWebhookHandler.test.d.ts +0 -16
- package/dist/tests/billing/StripeWebhookHandler.test.d.ts.map +0 -1
- package/dist/tests/billing/StripeWebhookHandler.test.js +0 -240
- package/dist/tests/billing/StripeWebhookHandler.test.js.map +0 -1
- package/dist/tests/billing/stripe-helpers.test.d.ts +0 -7
- package/dist/tests/billing/stripe-helpers.test.d.ts.map +0 -1
- package/dist/tests/billing/stripe-helpers.test.js +0 -91
- package/dist/tests/billing/stripe-helpers.test.js.map +0 -1
- package/dist/tests/billing/stripe-validators.test.d.ts.map +0 -1
- package/dist/tests/billing/stripe-validators.test.js.map +0 -1
- package/dist/tests/billing/webhook-handlers.test.d.ts +0 -16
- package/dist/tests/billing/webhook-handlers.test.d.ts.map +0 -1
- package/dist/tests/billing/webhook-handlers.test.js +0 -519
- package/dist/tests/billing/webhook-handlers.test.js.map +0 -1
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-3415: Stripe Helper Functions Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for mapSubscriptionStatus and getPriceIdForTier utilities.
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect } from 'vitest';
|
|
7
|
-
import { mapSubscriptionStatus, getPriceIdForTier } from '../../src/billing/stripe-helpers.js';
|
|
8
|
-
// ============================================================================
|
|
9
|
-
// Test Fixtures
|
|
10
|
-
// ============================================================================
|
|
11
|
-
function createTestPrices() {
|
|
12
|
-
return {
|
|
13
|
-
individual: {
|
|
14
|
-
monthly: 'price_ind_monthly',
|
|
15
|
-
annual: 'price_ind_annual',
|
|
16
|
-
},
|
|
17
|
-
team: {
|
|
18
|
-
monthly: 'price_team_monthly',
|
|
19
|
-
annual: 'price_team_annual',
|
|
20
|
-
},
|
|
21
|
-
enterprise: {
|
|
22
|
-
monthly: 'price_ent_monthly',
|
|
23
|
-
annual: 'price_ent_annual',
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// mapSubscriptionStatus Tests
|
|
29
|
-
// ============================================================================
|
|
30
|
-
describe('mapSubscriptionStatus', () => {
|
|
31
|
-
it('should map active status', () => {
|
|
32
|
-
expect(mapSubscriptionStatus('active')).toBe('active');
|
|
33
|
-
});
|
|
34
|
-
it('should map past_due status', () => {
|
|
35
|
-
expect(mapSubscriptionStatus('past_due')).toBe('past_due');
|
|
36
|
-
});
|
|
37
|
-
it('should map canceled status', () => {
|
|
38
|
-
expect(mapSubscriptionStatus('canceled')).toBe('canceled');
|
|
39
|
-
});
|
|
40
|
-
it('should map trialing status', () => {
|
|
41
|
-
expect(mapSubscriptionStatus('trialing')).toBe('trialing');
|
|
42
|
-
});
|
|
43
|
-
it('should map paused status', () => {
|
|
44
|
-
expect(mapSubscriptionStatus('paused')).toBe('paused');
|
|
45
|
-
});
|
|
46
|
-
it('should map incomplete status', () => {
|
|
47
|
-
expect(mapSubscriptionStatus('incomplete')).toBe('incomplete');
|
|
48
|
-
});
|
|
49
|
-
it('should map incomplete_expired status', () => {
|
|
50
|
-
expect(mapSubscriptionStatus('incomplete_expired')).toBe('incomplete_expired');
|
|
51
|
-
});
|
|
52
|
-
it('should map unpaid status', () => {
|
|
53
|
-
expect(mapSubscriptionStatus('unpaid')).toBe('unpaid');
|
|
54
|
-
});
|
|
55
|
-
it('should default to active for unknown status', () => {
|
|
56
|
-
// Cast to bypass type safety for edge case testing
|
|
57
|
-
expect(mapSubscriptionStatus('unknown_status')).toBe('active');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
// ============================================================================
|
|
61
|
-
// getPriceIdForTier Tests
|
|
62
|
-
// ============================================================================
|
|
63
|
-
describe('getPriceIdForTier', () => {
|
|
64
|
-
const prices = createTestPrices();
|
|
65
|
-
it('should return null for community tier', () => {
|
|
66
|
-
expect(getPriceIdForTier(prices, 'community', 'monthly')).toBeNull();
|
|
67
|
-
expect(getPriceIdForTier(prices, 'community', 'annual')).toBeNull();
|
|
68
|
-
});
|
|
69
|
-
it('should return monthly price for individual tier', () => {
|
|
70
|
-
expect(getPriceIdForTier(prices, 'individual', 'monthly')).toBe('price_ind_monthly');
|
|
71
|
-
});
|
|
72
|
-
it('should return annual price for individual tier', () => {
|
|
73
|
-
expect(getPriceIdForTier(prices, 'individual', 'annual')).toBe('price_ind_annual');
|
|
74
|
-
});
|
|
75
|
-
it('should return monthly price for team tier', () => {
|
|
76
|
-
expect(getPriceIdForTier(prices, 'team', 'monthly')).toBe('price_team_monthly');
|
|
77
|
-
});
|
|
78
|
-
it('should return annual price for team tier', () => {
|
|
79
|
-
expect(getPriceIdForTier(prices, 'team', 'annual')).toBe('price_team_annual');
|
|
80
|
-
});
|
|
81
|
-
it('should return monthly price for enterprise tier', () => {
|
|
82
|
-
expect(getPriceIdForTier(prices, 'enterprise', 'monthly')).toBe('price_ent_monthly');
|
|
83
|
-
});
|
|
84
|
-
it('should return annual price for enterprise tier', () => {
|
|
85
|
-
expect(getPriceIdForTier(prices, 'enterprise', 'annual')).toBe('price_ent_annual');
|
|
86
|
-
});
|
|
87
|
-
it('should return null for unknown tier', () => {
|
|
88
|
-
expect(getPriceIdForTier(prices, 'unknown', 'monthly')).toBeNull();
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
//# sourceMappingURL=stripe-helpers.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stripe-helpers.test.js","sourceRoot":"","sources":["../../../tests/billing/stripe-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAA;AAI9F,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,SAAS,gBAAgB;IACvB,OAAO;QACL,UAAU,EAAE;YACV,OAAO,EAAE,mBAAoC;YAC7C,MAAM,EAAE,kBAAmC;SAC5C;QACD,IAAI,EAAE;YACJ,OAAO,EAAE,oBAAqC;YAC9C,MAAM,EAAE,mBAAoC;SAC7C;QACD,UAAU,EAAE;YACV,OAAO,EAAE,mBAAoC;YAC7C,MAAM,EAAE,kBAAmC;SAC5C;KACF,CAAA;AACH,CAAC;AAED,+EAA+E;AAC/E,8BAA8B;AAC9B,+EAA+E;AAE/E,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,qBAAqB,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;IAChF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,mDAAmD;QACnD,MAAM,CAAC,qBAAqB,CAAC,gBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,+EAA+E;AAC/E,0BAA0B;AAC1B,+EAA+E;AAE/E,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAA;IAEjC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;QACpE,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;IACtF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;IACtF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IACpF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,SAAkB,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC7E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stripe-validators.test.d.ts","sourceRoot":"","sources":["../../../tests/billing/stripe-validators.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stripe-validators.test.js","sourceRoot":"","sources":["../../../tests/billing/stripe-validators.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-3415: Webhook Handler Functions Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for individual webhook event handler functions:
|
|
5
|
-
* - handleSubscriptionCreated (with/without license key, email)
|
|
6
|
-
* - handleSubscriptionUpdated (existing sub, tier change, new sub fallback)
|
|
7
|
-
* - handleSubscriptionDeleted (with email, without existing sub)
|
|
8
|
-
* - handleInvoicePaymentSucceeded (store invoice, missing customer)
|
|
9
|
-
* - handleInvoicePaymentFailed (store + email, missing customer)
|
|
10
|
-
* - handleCheckoutSessionCompleted (logging-only)
|
|
11
|
-
* - storeLicenseKey / revokeLicenseKey (DB operations)
|
|
12
|
-
* - extractTier / extractSeatCount / getCurrentPeriodEnd / getCurrentPeriodStart
|
|
13
|
-
* - extractSubscriptionIdFromInvoice
|
|
14
|
-
*/
|
|
15
|
-
export {};
|
|
16
|
-
//# sourceMappingURL=webhook-handlers.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-handlers.test.d.ts","sourceRoot":"","sources":["../../../tests/billing/webhook-handlers.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|
|
@@ -1,519 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SMI-3415: Webhook Handler Functions Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for individual webhook event handler functions:
|
|
5
|
-
* - handleSubscriptionCreated (with/without license key, email)
|
|
6
|
-
* - handleSubscriptionUpdated (existing sub, tier change, new sub fallback)
|
|
7
|
-
* - handleSubscriptionDeleted (with email, without existing sub)
|
|
8
|
-
* - handleInvoicePaymentSucceeded (store invoice, missing customer)
|
|
9
|
-
* - handleInvoicePaymentFailed (store + email, missing customer)
|
|
10
|
-
* - handleCheckoutSessionCompleted (logging-only)
|
|
11
|
-
* - storeLicenseKey / revokeLicenseKey (DB operations)
|
|
12
|
-
* - extractTier / extractSeatCount / getCurrentPeriodEnd / getCurrentPeriodStart
|
|
13
|
-
* - extractSubscriptionIdFromInvoice
|
|
14
|
-
*/
|
|
15
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
16
|
-
import { handleSubscriptionCreated, handleSubscriptionUpdated, handleSubscriptionDeleted, handleInvoicePaymentSucceeded, handleInvoicePaymentFailed, handleCheckoutSessionCompleted, storeLicenseKey, revokeLicenseKey, extractTier, extractSeatCount, getCurrentPeriodEnd, getCurrentPeriodStart, extractSubscriptionIdFromInvoice, } from '../../src/billing/webhook-handlers.js';
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Test Fixtures
|
|
19
|
-
// ============================================================================
|
|
20
|
-
function createMockDb() {
|
|
21
|
-
const runFn = vi.fn();
|
|
22
|
-
return {
|
|
23
|
-
prepare: vi.fn().mockReturnValue({ run: runFn, get: vi.fn(), all: vi.fn() }),
|
|
24
|
-
exec: vi.fn(),
|
|
25
|
-
close: vi.fn(),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function createMockStripeClient() {
|
|
29
|
-
return {
|
|
30
|
-
getCustomer: vi.fn(),
|
|
31
|
-
verifyWebhookSignature: vi.fn(),
|
|
32
|
-
mapSubscriptionStatus: vi.fn(),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function createMockBillingService() {
|
|
36
|
-
return {
|
|
37
|
-
upsertSubscription: vi.fn().mockReturnValue({ id: 'local_sub_1', tier: 'individual' }),
|
|
38
|
-
getSubscriptionByStripeId: vi.fn().mockReturnValue(null),
|
|
39
|
-
updateSubscriptionStatus: vi.fn(),
|
|
40
|
-
storeInvoice: vi.fn(),
|
|
41
|
-
isEventProcessed: vi.fn().mockReturnValue(false),
|
|
42
|
-
recordWebhookEvent: vi.fn(),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function createContext(overrides = {}) {
|
|
46
|
-
return {
|
|
47
|
-
stripe: createMockStripeClient(),
|
|
48
|
-
billing: createMockBillingService(),
|
|
49
|
-
db: createMockDb(),
|
|
50
|
-
...overrides,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function makeSubscription(overrides = {}) {
|
|
54
|
-
return {
|
|
55
|
-
id: 'sub_test_1',
|
|
56
|
-
customer: 'cus_test_1',
|
|
57
|
-
status: 'active',
|
|
58
|
-
metadata: { tier: 'team', seatCount: '5' },
|
|
59
|
-
items: {
|
|
60
|
-
data: [
|
|
61
|
-
{
|
|
62
|
-
id: 'si_1',
|
|
63
|
-
price: { id: 'price_team_monthly' },
|
|
64
|
-
quantity: 5,
|
|
65
|
-
current_period_start: 1700000000,
|
|
66
|
-
current_period_end: 1702592000,
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
canceled_at: null,
|
|
71
|
-
...overrides,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function makeInvoice(overrides = {}) {
|
|
75
|
-
return {
|
|
76
|
-
id: 'in_test_1',
|
|
77
|
-
customer: 'cus_test_1',
|
|
78
|
-
amount_paid: 2500,
|
|
79
|
-
amount_due: 2500,
|
|
80
|
-
currency: 'usd',
|
|
81
|
-
number: 'INV-001',
|
|
82
|
-
invoice_pdf: 'https://pdf.url',
|
|
83
|
-
hosted_invoice_url: 'https://hosted.url',
|
|
84
|
-
status_transitions: { paid_at: 1700000000 },
|
|
85
|
-
period_start: 1700000000,
|
|
86
|
-
period_end: 1702592000,
|
|
87
|
-
parent: {
|
|
88
|
-
subscription_details: {
|
|
89
|
-
subscription: 'sub_test_1',
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
...overrides,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
// ============================================================================
|
|
96
|
-
// Pure Helper Functions
|
|
97
|
-
// ============================================================================
|
|
98
|
-
describe('extractTier', () => {
|
|
99
|
-
it('should extract tier from metadata', () => {
|
|
100
|
-
const sub = makeSubscription({ metadata: { tier: 'enterprise' } });
|
|
101
|
-
expect(extractTier(sub)).toBe('enterprise');
|
|
102
|
-
});
|
|
103
|
-
it('should accept all valid tiers from metadata', () => {
|
|
104
|
-
for (const tier of ['community', 'individual', 'team', 'enterprise']) {
|
|
105
|
-
const sub = makeSubscription({ metadata: { tier } });
|
|
106
|
-
expect(extractTier(sub)).toBe(tier);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
it('should default to individual for missing metadata', () => {
|
|
110
|
-
const sub = makeSubscription({ metadata: {} });
|
|
111
|
-
expect(extractTier(sub)).toBe('individual');
|
|
112
|
-
});
|
|
113
|
-
it('should default to individual for invalid tier in metadata', () => {
|
|
114
|
-
const sub = makeSubscription({ metadata: { tier: 'platinum' } });
|
|
115
|
-
expect(extractTier(sub)).toBe('individual');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
describe('extractSeatCount', () => {
|
|
119
|
-
it('should extract seat count from metadata', () => {
|
|
120
|
-
const sub = makeSubscription({ metadata: { seatCount: '10' } });
|
|
121
|
-
expect(extractSeatCount(sub)).toBe(10);
|
|
122
|
-
});
|
|
123
|
-
it('should fall back to item quantity when metadata missing', () => {
|
|
124
|
-
const sub = makeSubscription({ metadata: {} });
|
|
125
|
-
expect(extractSeatCount(sub)).toBe(5); // from fixture item quantity
|
|
126
|
-
});
|
|
127
|
-
it('should fall back to item quantity for non-numeric metadata', () => {
|
|
128
|
-
const sub = makeSubscription({ metadata: { seatCount: 'abc' } });
|
|
129
|
-
expect(extractSeatCount(sub)).toBe(5);
|
|
130
|
-
});
|
|
131
|
-
it('should fall back to item quantity for zero seat count', () => {
|
|
132
|
-
const sub = makeSubscription({ metadata: { seatCount: '0' } });
|
|
133
|
-
expect(extractSeatCount(sub)).toBe(5);
|
|
134
|
-
});
|
|
135
|
-
it('should fall back to item quantity for negative seat count', () => {
|
|
136
|
-
const sub = makeSubscription({ metadata: { seatCount: '-1' } });
|
|
137
|
-
expect(extractSeatCount(sub)).toBe(5);
|
|
138
|
-
});
|
|
139
|
-
it('should return 1 when no items and no metadata', () => {
|
|
140
|
-
const sub = makeSubscription({
|
|
141
|
-
metadata: {},
|
|
142
|
-
items: { data: [{ quantity: undefined, current_period_end: 1702592000 }] },
|
|
143
|
-
});
|
|
144
|
-
expect(extractSeatCount(sub)).toBe(1);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
describe('getCurrentPeriodEnd', () => {
|
|
148
|
-
it('should return period end from first item', () => {
|
|
149
|
-
const sub = makeSubscription();
|
|
150
|
-
expect(getCurrentPeriodEnd(sub)).toBe(1702592000);
|
|
151
|
-
});
|
|
152
|
-
it('should return current timestamp when no items', () => {
|
|
153
|
-
const sub = makeSubscription({ items: { data: [] } });
|
|
154
|
-
const now = Math.floor(Date.now() / 1000);
|
|
155
|
-
const result = getCurrentPeriodEnd(sub);
|
|
156
|
-
// Should be within 2 seconds of now
|
|
157
|
-
expect(Math.abs(result - now)).toBeLessThan(2);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
describe('getCurrentPeriodStart', () => {
|
|
161
|
-
it('should return period start from first item', () => {
|
|
162
|
-
const sub = makeSubscription();
|
|
163
|
-
expect(getCurrentPeriodStart(sub)).toBe(1700000000);
|
|
164
|
-
});
|
|
165
|
-
it('should return current timestamp when no items', () => {
|
|
166
|
-
const sub = makeSubscription({ items: { data: [] } });
|
|
167
|
-
const now = Math.floor(Date.now() / 1000);
|
|
168
|
-
const result = getCurrentPeriodStart(sub);
|
|
169
|
-
expect(Math.abs(result - now)).toBeLessThan(2);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
describe('extractSubscriptionIdFromInvoice', () => {
|
|
173
|
-
it('should extract string subscription ID', () => {
|
|
174
|
-
const invoice = makeInvoice();
|
|
175
|
-
expect(extractSubscriptionIdFromInvoice(invoice)).toBe('sub_test_1');
|
|
176
|
-
});
|
|
177
|
-
it('should extract ID from subscription object', () => {
|
|
178
|
-
const invoice = makeInvoice({
|
|
179
|
-
parent: {
|
|
180
|
-
subscription_details: {
|
|
181
|
-
subscription: { id: 'sub_obj_1' },
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
expect(extractSubscriptionIdFromInvoice(invoice)).toBe('sub_obj_1');
|
|
186
|
-
});
|
|
187
|
-
it('should return undefined when no parent', () => {
|
|
188
|
-
const invoice = makeInvoice({ parent: undefined });
|
|
189
|
-
expect(extractSubscriptionIdFromInvoice(invoice)).toBeUndefined();
|
|
190
|
-
});
|
|
191
|
-
it('should return undefined when no subscription_details', () => {
|
|
192
|
-
const invoice = makeInvoice({ parent: {} });
|
|
193
|
-
expect(extractSubscriptionIdFromInvoice(invoice)).toBeUndefined();
|
|
194
|
-
});
|
|
195
|
-
it('should return undefined when subscription is null', () => {
|
|
196
|
-
const invoice = makeInvoice({
|
|
197
|
-
parent: { subscription_details: { subscription: null } },
|
|
198
|
-
});
|
|
199
|
-
expect(extractSubscriptionIdFromInvoice(invoice)).toBeUndefined();
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
// ============================================================================
|
|
203
|
-
// License Key DB Operations
|
|
204
|
-
// ============================================================================
|
|
205
|
-
describe('storeLicenseKey', () => {
|
|
206
|
-
it('should insert a license key record', () => {
|
|
207
|
-
const db = createMockDb();
|
|
208
|
-
const runFn = vi.fn();
|
|
209
|
-
vi.mocked(db.prepare).mockReturnValue({ run: runFn });
|
|
210
|
-
storeLicenseKey(db, {
|
|
211
|
-
subscriptionId: 'sub_1',
|
|
212
|
-
organizationId: 'org_1',
|
|
213
|
-
keyJwt: 'jwt_token_value',
|
|
214
|
-
keyExpiry: new Date('2026-12-31'),
|
|
215
|
-
});
|
|
216
|
-
expect(db.prepare).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO license_keys'));
|
|
217
|
-
expect(runFn).toHaveBeenCalledWith(expect.any(String), // UUID
|
|
218
|
-
'sub_1', 'org_1', 'jwt_token_value', expect.any(String), // SHA-256 hash
|
|
219
|
-
'2026-12-31T00:00:00.000Z', expect.any(String) // generated_at ISO
|
|
220
|
-
);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
describe('revokeLicenseKey', () => {
|
|
224
|
-
it('should update license key to inactive', () => {
|
|
225
|
-
const db = createMockDb();
|
|
226
|
-
const runFn = vi.fn();
|
|
227
|
-
vi.mocked(db.prepare).mockReturnValue({ run: runFn });
|
|
228
|
-
revokeLicenseKey(db, 'sub_1', 'tier_change');
|
|
229
|
-
expect(db.prepare).toHaveBeenCalledWith(expect.stringContaining('UPDATE license_keys'));
|
|
230
|
-
expect(runFn).toHaveBeenCalledWith(expect.any(String), 'tier_change', 'sub_1');
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
// ============================================================================
|
|
234
|
-
// handleSubscriptionCreated
|
|
235
|
-
// ============================================================================
|
|
236
|
-
describe('handleSubscriptionCreated', () => {
|
|
237
|
-
it('should create subscription record when customer found', async () => {
|
|
238
|
-
const ctx = createContext();
|
|
239
|
-
const sub = makeSubscription();
|
|
240
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
241
|
-
id: 'cus_test_1',
|
|
242
|
-
email: 'user@example.com',
|
|
243
|
-
deleted: false,
|
|
244
|
-
});
|
|
245
|
-
await handleSubscriptionCreated(ctx, sub);
|
|
246
|
-
expect(ctx.billing.upsertSubscription).toHaveBeenCalledWith(expect.objectContaining({
|
|
247
|
-
customerId: 'cus_test_1',
|
|
248
|
-
email: 'user@example.com',
|
|
249
|
-
stripeSubscriptionId: 'sub_test_1',
|
|
250
|
-
tier: 'team',
|
|
251
|
-
seatCount: 5,
|
|
252
|
-
}));
|
|
253
|
-
});
|
|
254
|
-
it('should throw when customer not found', async () => {
|
|
255
|
-
const ctx = createContext();
|
|
256
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue(null);
|
|
257
|
-
await expect(handleSubscriptionCreated(ctx, makeSubscription())).rejects.toThrow('Customer not found');
|
|
258
|
-
});
|
|
259
|
-
it('should generate and store license key for active subscription', async () => {
|
|
260
|
-
const onLicenseKeyNeeded = vi.fn().mockResolvedValue('jwt_license_123');
|
|
261
|
-
const db = createMockDb();
|
|
262
|
-
const runFn = vi.fn();
|
|
263
|
-
vi.mocked(db.prepare).mockReturnValue({ run: runFn });
|
|
264
|
-
const ctx = createContext({ onLicenseKeyNeeded, db });
|
|
265
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
266
|
-
id: 'cus_test_1',
|
|
267
|
-
email: 'user@example.com',
|
|
268
|
-
deleted: false,
|
|
269
|
-
});
|
|
270
|
-
await handleSubscriptionCreated(ctx, makeSubscription({ status: 'active' }));
|
|
271
|
-
expect(onLicenseKeyNeeded).toHaveBeenCalledWith(expect.objectContaining({
|
|
272
|
-
customerId: 'cus_test_1',
|
|
273
|
-
tier: 'team',
|
|
274
|
-
}));
|
|
275
|
-
expect(db.prepare).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO license_keys'));
|
|
276
|
-
});
|
|
277
|
-
it('should send license key email when onEmailNeeded provided', async () => {
|
|
278
|
-
const onLicenseKeyNeeded = vi.fn().mockResolvedValue('jwt_license_456');
|
|
279
|
-
const onEmailNeeded = vi.fn().mockResolvedValue(undefined);
|
|
280
|
-
const db = createMockDb();
|
|
281
|
-
vi.mocked(db.prepare).mockReturnValue({ run: vi.fn() });
|
|
282
|
-
const ctx = createContext({ onLicenseKeyNeeded, onEmailNeeded, db });
|
|
283
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
284
|
-
id: 'cus_test_1',
|
|
285
|
-
email: 'user@example.com',
|
|
286
|
-
deleted: false,
|
|
287
|
-
});
|
|
288
|
-
await handleSubscriptionCreated(ctx, makeSubscription({ status: 'active' }));
|
|
289
|
-
expect(onEmailNeeded).toHaveBeenCalledWith(expect.objectContaining({
|
|
290
|
-
type: 'license_key',
|
|
291
|
-
email: 'user@example.com',
|
|
292
|
-
data: expect.objectContaining({ licenseKey: 'jwt_license_456', tier: 'team' }),
|
|
293
|
-
}));
|
|
294
|
-
});
|
|
295
|
-
it('should skip license key for non-active subscription', async () => {
|
|
296
|
-
const onLicenseKeyNeeded = vi.fn();
|
|
297
|
-
const ctx = createContext({ onLicenseKeyNeeded });
|
|
298
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
299
|
-
id: 'cus_test_1',
|
|
300
|
-
email: 'user@example.com',
|
|
301
|
-
deleted: false,
|
|
302
|
-
});
|
|
303
|
-
await handleSubscriptionCreated(ctx, makeSubscription({ status: 'trialing' }));
|
|
304
|
-
expect(onLicenseKeyNeeded).not.toHaveBeenCalled();
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
// ============================================================================
|
|
308
|
-
// handleSubscriptionUpdated
|
|
309
|
-
// ============================================================================
|
|
310
|
-
describe('handleSubscriptionUpdated', () => {
|
|
311
|
-
it('should create subscription if not found locally', async () => {
|
|
312
|
-
const ctx = createContext();
|
|
313
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue(null);
|
|
314
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
315
|
-
id: 'cus_test_1',
|
|
316
|
-
email: 'user@example.com',
|
|
317
|
-
deleted: false,
|
|
318
|
-
});
|
|
319
|
-
await handleSubscriptionUpdated(ctx, makeSubscription());
|
|
320
|
-
expect(ctx.billing.upsertSubscription).toHaveBeenCalled();
|
|
321
|
-
});
|
|
322
|
-
it('should update status for existing subscription', async () => {
|
|
323
|
-
const ctx = createContext();
|
|
324
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue({
|
|
325
|
-
id: 'local_sub_1',
|
|
326
|
-
tier: 'team',
|
|
327
|
-
});
|
|
328
|
-
const sub = makeSubscription({ status: 'past_due', canceled_at: null });
|
|
329
|
-
await handleSubscriptionUpdated(ctx, sub);
|
|
330
|
-
expect(ctx.billing.updateSubscriptionStatus).toHaveBeenCalledWith('sub_test_1', 'past_due', null);
|
|
331
|
-
});
|
|
332
|
-
it('should pass canceled_at date when present', async () => {
|
|
333
|
-
const ctx = createContext();
|
|
334
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue({
|
|
335
|
-
id: 'local_sub_1',
|
|
336
|
-
tier: 'team',
|
|
337
|
-
});
|
|
338
|
-
const sub = makeSubscription({ status: 'canceled', canceled_at: 1700000000 });
|
|
339
|
-
await handleSubscriptionUpdated(ctx, sub);
|
|
340
|
-
expect(ctx.billing.updateSubscriptionStatus).toHaveBeenCalledWith('sub_test_1', 'canceled', new Date(1700000000 * 1000));
|
|
341
|
-
});
|
|
342
|
-
it('should regenerate license key on tier change', async () => {
|
|
343
|
-
const onLicenseKeyNeeded = vi.fn().mockResolvedValue('new_jwt');
|
|
344
|
-
const db = createMockDb();
|
|
345
|
-
const runFn = vi.fn();
|
|
346
|
-
vi.mocked(db.prepare).mockReturnValue({ run: runFn });
|
|
347
|
-
const ctx = createContext({ onLicenseKeyNeeded, db });
|
|
348
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue({
|
|
349
|
-
id: 'local_sub_1',
|
|
350
|
-
tier: 'individual', // old tier
|
|
351
|
-
});
|
|
352
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
353
|
-
id: 'cus_test_1',
|
|
354
|
-
email: 'user@example.com',
|
|
355
|
-
});
|
|
356
|
-
// New tier is 'team' from metadata
|
|
357
|
-
await handleSubscriptionUpdated(ctx, makeSubscription({ metadata: { tier: 'team' } }));
|
|
358
|
-
// Should revoke old key
|
|
359
|
-
expect(db.prepare).toHaveBeenCalledWith(expect.stringContaining('UPDATE license_keys'));
|
|
360
|
-
// Should generate new key
|
|
361
|
-
expect(onLicenseKeyNeeded).toHaveBeenCalledWith(expect.objectContaining({ tier: 'team' }));
|
|
362
|
-
// Should store new key
|
|
363
|
-
expect(db.prepare).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO license_keys'));
|
|
364
|
-
});
|
|
365
|
-
it('should not regenerate license key when tier unchanged', async () => {
|
|
366
|
-
const onLicenseKeyNeeded = vi.fn();
|
|
367
|
-
const ctx = createContext({ onLicenseKeyNeeded });
|
|
368
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue({
|
|
369
|
-
id: 'local_sub_1',
|
|
370
|
-
tier: 'team', // same as metadata
|
|
371
|
-
});
|
|
372
|
-
await handleSubscriptionUpdated(ctx, makeSubscription({ metadata: { tier: 'team' } }));
|
|
373
|
-
expect(onLicenseKeyNeeded).not.toHaveBeenCalled();
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
// ============================================================================
|
|
377
|
-
// handleSubscriptionDeleted
|
|
378
|
-
// ============================================================================
|
|
379
|
-
describe('handleSubscriptionDeleted', () => {
|
|
380
|
-
it('should cancel subscription and revoke license key', async () => {
|
|
381
|
-
const db = createMockDb();
|
|
382
|
-
const runFn = vi.fn();
|
|
383
|
-
vi.mocked(db.prepare).mockReturnValue({ run: runFn });
|
|
384
|
-
const ctx = createContext({ db });
|
|
385
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue({
|
|
386
|
-
id: 'local_sub_1',
|
|
387
|
-
tier: 'team',
|
|
388
|
-
});
|
|
389
|
-
await handleSubscriptionDeleted(ctx, makeSubscription());
|
|
390
|
-
expect(ctx.billing.updateSubscriptionStatus).toHaveBeenCalledWith('sub_test_1', 'canceled', expect.any(Date));
|
|
391
|
-
expect(db.prepare).toHaveBeenCalledWith(expect.stringContaining('UPDATE license_keys'));
|
|
392
|
-
});
|
|
393
|
-
it('should send cancellation email', async () => {
|
|
394
|
-
const onEmailNeeded = vi.fn().mockResolvedValue(undefined);
|
|
395
|
-
const db = createMockDb();
|
|
396
|
-
vi.mocked(db.prepare).mockReturnValue({ run: vi.fn() });
|
|
397
|
-
const ctx = createContext({ onEmailNeeded, db });
|
|
398
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue({
|
|
399
|
-
id: 'local_sub_1',
|
|
400
|
-
tier: 'team',
|
|
401
|
-
});
|
|
402
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
403
|
-
id: 'cus_test_1',
|
|
404
|
-
email: 'user@example.com',
|
|
405
|
-
});
|
|
406
|
-
await handleSubscriptionDeleted(ctx, makeSubscription());
|
|
407
|
-
expect(onEmailNeeded).toHaveBeenCalledWith(expect.objectContaining({
|
|
408
|
-
type: 'subscription_canceled',
|
|
409
|
-
email: 'user@example.com',
|
|
410
|
-
}));
|
|
411
|
-
});
|
|
412
|
-
it('should do nothing when subscription not found locally', async () => {
|
|
413
|
-
const ctx = createContext();
|
|
414
|
-
vi.mocked(ctx.billing.getSubscriptionByStripeId).mockReturnValue(null);
|
|
415
|
-
await handleSubscriptionDeleted(ctx, makeSubscription());
|
|
416
|
-
expect(ctx.billing.updateSubscriptionStatus).not.toHaveBeenCalled();
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
// ============================================================================
|
|
420
|
-
// handleInvoicePaymentSucceeded
|
|
421
|
-
// ============================================================================
|
|
422
|
-
describe('handleInvoicePaymentSucceeded', () => {
|
|
423
|
-
it('should store paid invoice', async () => {
|
|
424
|
-
const ctx = createContext();
|
|
425
|
-
const invoice = makeInvoice();
|
|
426
|
-
await handleInvoicePaymentSucceeded(ctx, invoice);
|
|
427
|
-
expect(ctx.billing.storeInvoice).toHaveBeenCalledWith(expect.objectContaining({
|
|
428
|
-
customerId: 'cus_test_1',
|
|
429
|
-
stripeInvoiceId: 'in_test_1',
|
|
430
|
-
status: 'paid',
|
|
431
|
-
amountCents: 2500,
|
|
432
|
-
currency: 'usd',
|
|
433
|
-
subscriptionId: 'sub_test_1',
|
|
434
|
-
}));
|
|
435
|
-
});
|
|
436
|
-
it('should handle customer as object', async () => {
|
|
437
|
-
const ctx = createContext();
|
|
438
|
-
const invoice = makeInvoice({ customer: { id: 'cus_obj_1' } });
|
|
439
|
-
await handleInvoicePaymentSucceeded(ctx, invoice);
|
|
440
|
-
expect(ctx.billing.storeInvoice).toHaveBeenCalledWith(expect.objectContaining({ customerId: 'cus_obj_1' }));
|
|
441
|
-
});
|
|
442
|
-
it('should skip when no customer ID', async () => {
|
|
443
|
-
const ctx = createContext();
|
|
444
|
-
const invoice = makeInvoice({ customer: null });
|
|
445
|
-
await handleInvoicePaymentSucceeded(ctx, invoice);
|
|
446
|
-
expect(ctx.billing.storeInvoice).not.toHaveBeenCalled();
|
|
447
|
-
});
|
|
448
|
-
it('should handle missing status_transitions.paid_at', async () => {
|
|
449
|
-
const ctx = createContext();
|
|
450
|
-
const invoice = makeInvoice({ status_transitions: {} });
|
|
451
|
-
await handleInvoicePaymentSucceeded(ctx, invoice);
|
|
452
|
-
expect(ctx.billing.storeInvoice).toHaveBeenCalledWith(expect.objectContaining({
|
|
453
|
-
paidAt: expect.any(Date),
|
|
454
|
-
}));
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
// ============================================================================
|
|
458
|
-
// handleInvoicePaymentFailed
|
|
459
|
-
// ============================================================================
|
|
460
|
-
describe('handleInvoicePaymentFailed', () => {
|
|
461
|
-
it('should store open invoice', async () => {
|
|
462
|
-
const ctx = createContext();
|
|
463
|
-
const invoice = makeInvoice();
|
|
464
|
-
await handleInvoicePaymentFailed(ctx, invoice);
|
|
465
|
-
expect(ctx.billing.storeInvoice).toHaveBeenCalledWith(expect.objectContaining({
|
|
466
|
-
customerId: 'cus_test_1',
|
|
467
|
-
stripeInvoiceId: 'in_test_1',
|
|
468
|
-
status: 'open',
|
|
469
|
-
amountCents: 2500,
|
|
470
|
-
}));
|
|
471
|
-
});
|
|
472
|
-
it('should send payment failed email', async () => {
|
|
473
|
-
const onEmailNeeded = vi.fn().mockResolvedValue(undefined);
|
|
474
|
-
const ctx = createContext({ onEmailNeeded });
|
|
475
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
476
|
-
id: 'cus_test_1',
|
|
477
|
-
email: 'user@example.com',
|
|
478
|
-
});
|
|
479
|
-
await handleInvoicePaymentFailed(ctx, makeInvoice());
|
|
480
|
-
expect(onEmailNeeded).toHaveBeenCalledWith(expect.objectContaining({
|
|
481
|
-
type: 'payment_failed',
|
|
482
|
-
email: 'user@example.com',
|
|
483
|
-
data: expect.objectContaining({
|
|
484
|
-
invoiceId: 'in_test_1',
|
|
485
|
-
amount: 2500,
|
|
486
|
-
}),
|
|
487
|
-
}));
|
|
488
|
-
});
|
|
489
|
-
it('should skip when no customer ID', async () => {
|
|
490
|
-
const ctx = createContext();
|
|
491
|
-
const invoice = makeInvoice({ customer: null });
|
|
492
|
-
await handleInvoicePaymentFailed(ctx, invoice);
|
|
493
|
-
expect(ctx.billing.storeInvoice).not.toHaveBeenCalled();
|
|
494
|
-
});
|
|
495
|
-
it('should skip email when customer has no email', async () => {
|
|
496
|
-
const onEmailNeeded = vi.fn();
|
|
497
|
-
const ctx = createContext({ onEmailNeeded });
|
|
498
|
-
vi.mocked(ctx.stripe.getCustomer).mockResolvedValue({
|
|
499
|
-
id: 'cus_test_1',
|
|
500
|
-
email: null,
|
|
501
|
-
});
|
|
502
|
-
await handleInvoicePaymentFailed(ctx, makeInvoice());
|
|
503
|
-
expect(onEmailNeeded).not.toHaveBeenCalled();
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
// ============================================================================
|
|
507
|
-
// handleCheckoutSessionCompleted
|
|
508
|
-
// ============================================================================
|
|
509
|
-
describe('handleCheckoutSessionCompleted', () => {
|
|
510
|
-
it('should not throw (logging-only handler)', () => {
|
|
511
|
-
const session = {
|
|
512
|
-
id: 'cs_test_1',
|
|
513
|
-
customer: 'cus_test_1',
|
|
514
|
-
subscription: 'sub_test_1',
|
|
515
|
-
};
|
|
516
|
-
expect(() => handleCheckoutSessionCompleted(session)).not.toThrow();
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
//# sourceMappingURL=webhook-handlers.test.js.map
|