Skip to content

Commit 62d2ee6

Browse files
committed
start hook separate process
1 parent 5854cfd commit 62d2ee6

File tree

4 files changed

+325
-2
lines changed

4 files changed

+325
-2
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"lint": "eslint -c .eslintrc.js lib",
2121
"release": "standard-version && git push --follow-tags origin && npm publish",
2222
"release-minor": "standard-version --release-as minor && git push --follow-tags origin && npm publish",
23-
"paddle-webhook-tunnel": "lt --port 3456 --subdomain xxrrii533vj7h9qipggbkbze"
23+
"paddle-webhook-tunnel": "lt --port 3456 --subdomain slsdkfenf88811xfe"
2424
},
2525
"repository": {
2626
"type": "git",
@@ -57,4 +57,4 @@
5757
"optionalDependencies": {
5858
"mongodb": "^6.8.0"
5959
}
60-
}
60+
}

test-e2e.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ function cancel {
1010

1111
trap cancel EXIT
1212

13+
npm run paddle-webhook-tunnel &
14+
sleep 5
1315
npm run test-e2e
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { SimpleResourceStorage as SimpleStorage } from '@discue/mongodb-resource-client'
2+
import { expect } from 'chai'
3+
import { randomUUID as uuid } from 'node:crypto'
4+
import EventStorage from '../../../lib/billing/event-log-storage.js'
5+
import SubscriptionStorage from '../../../lib/billing/subscription-hook-storage.js'
6+
import subscriptionCancelled from '../../billing-fixtues/subscription-canceled.js'
7+
import subscriptionCreated from '../../billing-fixtues/subscription-created.js'
8+
import mongoClient from "../mongodb-client.js"
9+
10+
const client = mongoClient()
11+
12+
const eventStorageBackend = new SimpleStorage({ collectionName: '_events', client })
13+
const eventStorage = new EventStorage({ storage: eventStorageBackend })
14+
15+
const subscriptionStorageBackend = new SimpleStorage({ collectionName: '_subscriptions', client })
16+
const subscriptionStorage = new SubscriptionStorage({ storage: subscriptionStorageBackend, eventStorage })
17+
18+
const historyStorageBackend = new SimpleStorage({ collectionName: '_subscriptions_history', client })
19+
20+
describe('SubscriptionHook Billing API', () => {
21+
const allIds = []
22+
23+
let nextId
24+
25+
beforeEach(() => {
26+
nextId = `sub_${uuid()}`
27+
allIds.push(nextId)
28+
})
29+
30+
afterEach(async () => {
31+
await subscriptionStorageBackend.delete(nextId)
32+
})
33+
34+
after(async () => {
35+
await subscriptionStorageBackend.close()
36+
await eventStorageBackend.close()
37+
await historyStorageBackend.close()
38+
})
39+
40+
after(async () => {
41+
await client.close()
42+
})
43+
44+
45+
it('creates a new subscription', async () => {
46+
const subscription = subscriptionCreated()
47+
subscription.data.id = nextId
48+
await subscriptionStorage.store(subscription)
49+
50+
const subscriptions = await subscriptionStorageBackend.getAll()
51+
expect(subscriptions).to.have.length(1)
52+
expect(subscriptions.at(0).id).to.match(/^sub_/)
53+
expect(subscriptions.at(0).id).to.equal(subscription.data.id)
54+
expect(subscriptions.at(0).status).to.equal('active')
55+
})
56+
57+
it('creates a hook log entry', async () => {
58+
const keysToCompare = ['event_id', 'event_type', 'notification_id', 'occurred_at', 'target_id']
59+
60+
const subscription = subscriptionCreated()
61+
subscription.data.id = nextId
62+
await subscriptionStorage.store(subscription)
63+
64+
const events = await eventStorageBackend.getAll()
65+
const hasOneExpectedEvent = events.some((event) => {
66+
return keysToCompare.every((key) => {
67+
if (key === 'target_id') {
68+
return true
69+
}
70+
return event[key] === subscription[key]
71+
})
72+
})
73+
74+
expect(hasOneExpectedEvent).to.equal(true)
75+
})
76+
77+
it('updates a subscription', async () => {
78+
const subscriptionCreatedData = subscriptionCreated()
79+
subscriptionCreatedData.data.id = nextId
80+
await subscriptionStorage.store(subscriptionCreatedData)
81+
82+
const subscription = subscriptionCancelled()
83+
subscription.data.id = nextId
84+
await subscriptionStorage.store(subscription)
85+
86+
const subscriptions = await subscriptionStorageBackend.getAll()
87+
expect(subscriptions).to.have.length(1)
88+
expect(subscriptions.at(0).id).to.match(/^sub_/)
89+
expect(subscriptions.at(0).id).to.equal(subscription.data.id)
90+
expect(subscriptions.at(0).status).to.equal('canceled')
91+
})
92+
93+
it('adds entries to history table', async () => {
94+
const subscriptionCreatedData = subscriptionCreated()
95+
subscriptionCreatedData.data.id = nextId
96+
await subscriptionStorage.store(subscriptionCreatedData)
97+
await new Promise((resolve) => setTimeout(resolve, 250))
98+
99+
const history = await historyStorageBackend.getAll()
100+
console.log(JSON.stringify(history, null, 2))
101+
history.forEach(h => console.log('sub', h.id))
102+
// console.log(JSON.stringify(history, null, 2))
103+
expect(history).to.have.length(allIds.length)
104+
})
105+
})
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import { SimpleResourceStorage as SimpleStorage } from '@discue/mongodb-resource-client'
2+
import { expect } from 'chai'
3+
import { randomUUID as uuid } from 'node:crypto'
4+
import EventStorage from '../../../lib/billing/event-log-storage.js'
5+
import SubscriptionStorage from '../../../lib/billing/subscription-hook-storage.js'
6+
import SubscriptionInfoStorage from '../../../lib/billing/subscription-info.js'
7+
import subscriptionCreated from '../../billing-fixtues/subscription-created.js'
8+
import mongoClient from "../mongodb-client.js"
9+
10+
const client = mongoClient()
11+
12+
const eventStorageBackend = new SimpleStorage({ collectionName: '_events', client })
13+
const eventStorage = new EventStorage({ storage: eventStorageBackend })
14+
15+
const subscriptionStorageBackend = new SimpleStorage({ collectionName: '_subscriptions', client })
16+
const subscriptionStorage = new SubscriptionStorage({ storage: subscriptionStorageBackend, eventStorage })
17+
18+
const subscriptionInfoBackend = new SimpleStorage({ collectionName: '_subscriptions', client })
19+
const subscriptionInfo = new SubscriptionInfoStorage({ storage: subscriptionInfoBackend, eventStorage })
20+
21+
describe('SubscriptionHook Billing API', () => {
22+
23+
let nextId
24+
25+
beforeEach(() => {
26+
nextId = `sub_${uuid()}`
27+
})
28+
29+
afterEach(async () => {
30+
await subscriptionStorageBackend.delete(nextId)
31+
})
32+
33+
after(async () => {
34+
await subscriptionStorageBackend.close()
35+
await eventStorageBackend.close()
36+
await subscriptionInfoBackend.close()
37+
})
38+
39+
after(async () => {
40+
return client.close()
41+
})
42+
43+
describe('.getSubscriptionsByCustomData', () => {
44+
it('returns a subscription by customData', async () => {
45+
const subscription = subscriptionCreated()
46+
subscription.data.id = nextId
47+
await subscriptionStorage.store(subscription)
48+
49+
const subs = await subscriptionInfo.getSubscriptionsByCustomData({
50+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
51+
})
52+
53+
expect(subs).to.have.length(1)
54+
expect(subs.at(0).id).to.equal(nextId)
55+
})
56+
57+
it('returns null if no subscription was found', async () => {
58+
const subscription = subscriptionCreated()
59+
subscription.data.id = nextId
60+
await subscriptionStorage.store(subscription)
61+
62+
const info = await subscriptionInfo.getSubscriptionsByCustomData({ ids: [123] })
63+
expect(info).to.have.length(0)
64+
})
65+
})
66+
67+
describe('.getStatusTrailByCustomData', () => {
68+
it('returns a history', async () => {
69+
const subscription = subscriptionCreated()
70+
subscription.data.id = nextId
71+
await subscriptionStorage.store(subscription)
72+
await new Promise((resolve) => setTimeout(resolve, 200))
73+
74+
const subs = await subscriptionInfo.getStatusTrailByCustomData({
75+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
76+
})
77+
78+
expect(subs).to.have.length(1)
79+
expect(subs.at(0).action).to.equal('create')
80+
})
81+
82+
it('returns null if no subscription was found', async () => {
83+
const subscription = subscriptionCreated()
84+
subscription.data.id = nextId
85+
await subscriptionStorage.store(subscription)
86+
87+
const info = await subscriptionInfo.getSubscriptionsByCustomData({ ids: [123] })
88+
expect(info).to.have.length(0)
89+
})
90+
})
91+
92+
describe('.getActiveProductsByCustomData', () => {
93+
it('returns empty list if no products active', async () => {
94+
const subscription = subscriptionCreated()
95+
subscription.data.id = nextId
96+
await subscriptionStorage.store(subscription)
97+
98+
const info = await subscriptionInfo.getActiveProductsByCustomData({
99+
ids: ["123"]
100+
})
101+
expect(Object.keys(info)).to.have.length(0)
102+
})
103+
104+
it('returns empty list if sub status is canceled', async () => {
105+
const subscription = subscriptionCreated()
106+
subscription.data.id = nextId
107+
subscription.data.status = 'canceled'
108+
await subscriptionStorage.store(subscription)
109+
110+
const info = await subscriptionInfo.getActiveProductsByCustomData({
111+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
112+
})
113+
expect(Object.keys(info)).to.have.length(0)
114+
})
115+
116+
it('returns empty list if sub status is paused', async () => {
117+
const subscription = subscriptionCreated()
118+
subscription.data.id = nextId
119+
subscription.data.status = 'paused'
120+
await subscriptionStorage.store(subscription)
121+
122+
const info = await subscriptionInfo.getActiveProductsByCustomData({
123+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
124+
})
125+
expect(Object.keys(info)).to.have.length(0)
126+
})
127+
128+
it('returns active products if sub status is active', async () => {
129+
const subscription = subscriptionCreated()
130+
subscription.data.id = nextId
131+
await subscriptionStorage.store(subscription)
132+
133+
const subs = await subscriptionInfo.getActiveProductsByCustomData({
134+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
135+
})
136+
137+
expect(subs).to.have.keys(subscription.data.id)
138+
const products = subs[subscription.data.id]
139+
140+
expect(products).to.have.length(2)
141+
expect(products).to.include('pro_01gsz4t5hdjse780zja8vvr7jg')
142+
expect(products).to.include('pro_01h1vjes1y163xfj1rh1tkfb65')
143+
})
144+
145+
it('returns active products if sub status is trialing', async () => {
146+
const subscription = subscriptionCreated()
147+
subscription.data.id = nextId
148+
subscription.data.status = 'trialing'
149+
await subscriptionStorage.store(subscription)
150+
151+
const subs = await subscriptionInfo.getActiveProductsByCustomData({
152+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
153+
})
154+
155+
expect(subs).to.have.keys(subscription.data.id)
156+
const products = subs[subscription.data.id]
157+
158+
expect(products).to.have.length(2)
159+
expect(products).to.include('pro_01gsz4t5hdjse780zja8vvr7jg')
160+
expect(products).to.include('pro_01h1vjes1y163xfj1rh1tkfb65')
161+
})
162+
163+
it('returns active products if sub status is past_due', async () => {
164+
const subscription = subscriptionCreated()
165+
subscription.data.id = nextId
166+
subscription.data.status = 'past_due'
167+
await subscriptionStorage.store(subscription)
168+
169+
const subs = await subscriptionInfo.getActiveProductsByCustomData({
170+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
171+
})
172+
173+
expect(subs).to.have.keys(subscription.data.id)
174+
const products = subs[subscription.data.id]
175+
176+
expect(products).to.have.length(2)
177+
expect(products).to.include('pro_01gsz4t5hdjse780zja8vvr7jg')
178+
expect(products).to.include('pro_01h1vjes1y163xfj1rh1tkfb65')
179+
})
180+
181+
it('does not return inactive products', async () => {
182+
const subscription = subscriptionCreated()
183+
subscription.data.id = nextId
184+
subscription.data.items[0].status = 'inactive'
185+
await subscriptionStorage.store(subscription)
186+
187+
const subs = await subscriptionInfo.getActiveProductsByCustomData({
188+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
189+
})
190+
191+
expect(subs).to.have.keys(subscription.data.id)
192+
const products = subs[subscription.data.id]
193+
194+
expect(products).to.have.length(1)
195+
expect(products).to.include('pro_01h1vjes1y163xfj1rh1tkfb65')
196+
})
197+
198+
it('does not return products with status trialing', async () => {
199+
const subscription = subscriptionCreated()
200+
subscription.data.id = nextId
201+
subscription.data.items[0].status = 'trialing'
202+
await subscriptionStorage.store(subscription)
203+
204+
const subs = await subscriptionInfo.getActiveProductsByCustomData({
205+
ids: ["77693b60-e2b5-404b-a7f0-51d4dda7284a"]
206+
})
207+
208+
expect(subs).to.have.keys(subscription.data.id)
209+
const products = subs[subscription.data.id]
210+
211+
expect(products).to.have.length(2)
212+
expect(products).to.include('pro_01gsz4t5hdjse780zja8vvr7jg')
213+
expect(products).to.include('pro_01h1vjes1y163xfj1rh1tkfb65')
214+
})
215+
})
216+
})

0 commit comments

Comments
 (0)