-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DRAFT: Add Integration and unit tests on Billing #9317
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR adds comprehensive test coverage for the billing functionality, focusing on Stripe webhook handling and data transformations.
- Added extensive unit tests in
/packages/twenty-server/src/engine/core-modules/billing/utils/__tests__/
covering all Stripe data transformation utilities with thorough edge cases - Added integration tests in
/packages/twenty-server/test/integration/billing/suites/billing-controller.integration-spec.ts
for webhook handling of product, price, and subscription events - Modified
nx.json
and.eslintrc.cjs
to properly configure integration test files with pattern*.integration-spec.ts
- Added mock StripeService in
create-app.ts
for webhook testing, though signature validation could be more robust - Incomplete test coverage for entitlement handling as noted by commented out test case
14 file(s) reviewed, 20 comment(s)
Edit PR Review Bot Settings | Greptile
it('should return true if metadata is empty', () => { | ||
const metadata: Stripe.Metadata = {}; | ||
|
||
expect(isStripeValidProductMetadata(metadata)).toBe(true); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Empty metadata returning true but missing required keys returning false (line 49-55) seems inconsistent. Should validate this is the intended behavior.
files: [ | ||
'*.spec.@(ts|tsx|js|jsx)', | ||
'*.integration-spec.@(ts|tsx|js|jsx)', | ||
'*.test.@(ts|tsx|js|jsx)', | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: test file patterns could be consolidated into a single pattern like '*.@(spec|integration-spec|test).@(ts|tsx|js|jsx)' for better maintainability
]); | ||
}); | ||
|
||
it('should return the SSO key with false value,should only render the values that are listed in BillingEntitlementKeys', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Test description contains a comma splice. Consider splitting into two test cases for clarity: one for SSO false value and one for BillingEntitlementKeys filtering
default_aggregation: { | ||
formula: 'sum', | ||
}, | ||
event_time_window: null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: test should also verify behavior with event_time_window='hour' since that's another valid value
mockData as any, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: avoid using 'any' type casting - should define proper type for mockData based on Stripe.PriceCreatedEvent.Data
await client | ||
.post('/billing/webhooks') | ||
.set('Authorization', `Bearer ${ACCESS_TOKEN}`) | ||
.set('stripe-signature', 'correct-signature') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Using 'correct-signature' as a static value doesn't properly test signature verification. Should use Stripe's signing secret to generate valid signatures for test payloads.
constructEventFromPayload: (signature: string, payload: Buffer) => { | ||
if (signature === 'correct-signature') { | ||
const body = JSON.parse(payload.toString()); | ||
|
||
return { | ||
type: body.type, | ||
data: body.data, | ||
}; | ||
} | ||
throw new Error('Invalid signature'); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Mock implementation is overly simplified and may miss edge cases. Consider using Stripe's testing webhooks or a more robust signature verification process.
verify: (req: any, res, buf) => { | ||
req.rawBody = buf; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: req is typed as 'any'. Consider creating a proper type extending Express.Request
packages/twenty-server/test/integration/billing/suites/billing-controller.integration-spec.ts
Outdated
Show resolved
Hide resolved
describe('BillingController (integration)', () => { | ||
const mockTimestamp = 1672531200; | ||
|
||
//put this in utils? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Mock data creation utilities should be moved to a shared test utils file for reuse across test suites
@@ -25,7 +28,21 @@ export const createApp = async ( | |||
): Promise<NestExpressApplication> => { | |||
let moduleBuilder: TestingModuleBuilder = Test.createTestingModule({ | |||
imports: [AppModule], | |||
}); | |||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed, let's initialize the app with the same parameters as the real app (i.e. cors true and rawbody true)
@@ -35,6 +52,14 @@ export const createApp = async ( | |||
|
|||
const app = moduleFixture.createNestApplication<NestExpressApplication>(); | |||
|
|||
app.use( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this
const client = request(`http://localhost:${APP_PORT}`); | ||
|
||
// TODO: Add more tests | ||
// THIS TEST ONLY WORKS IF THE BILLING TABLES ARE CREATED, VERIFY IS_BILLING_ENABLED IS TRUE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check ci-server.yaml
.set('stripe-signature', 'correct-signature') | ||
.set('Content-Type', 'application/json') | ||
.send(JSON.stringify(payload)) | ||
.expect(200); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're not testing much here when just checking the status code, could you check something else everytime you check a 200, for example modify the response so that it provides the id of the object we just inserted/modified, and then in the test you verify that the request returned a uuid as a response
…wentyhq/twenty into feat/add-unit-integration-test-billing
@@ -43,6 +43,8 @@ export class BillingController { | |||
@Req() req: RawBodyRequest<Request>, | |||
@Res() res: Response, | |||
) { | |||
let resultBody = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style notes:
You can instead create another method like handleStripeEvent
that will return the body to simplify your code
const result = await this.handleStripeEvent(event);
return res.status(200).send(result).end();
private async handleStripeEvent(event: WebhookEvent) {
switch (event.type) {
case WebhookEvent.SETUP_INTENT_SUCCEEDED:
return this.billingSubscriptionService.handleUnpaidInvoices(event.data);
case WebhookEvent.CUSTOMER_SUBSCRIPTION_CREATED:
case WebhookEvent.CUSTOMER_SUBSCRIPTION_UPDATED:
case WebhookEvent.CUSTOMER_SUBSCRIPTION_DELETED:
......
@@ -54,7 +56,9 @@ export class BillingController { | |||
); | |||
|
|||
if (event.type === WebhookEvent.SETUP_INTENT_SUCCEEDED) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this WebhookEvent to BillingWebhookEvent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Solves [ https://github.com/twentyhq/private-issues/issues/214 ]
TLDR
Add unit and integration tests to Billing. First approach to run jest integration tests directly from VSCode.
In order to run the unit tests:
Run unit test using the CLI or with the jest extension directly from VSCode.
In order to run the integration tests:
Ensure that your database has the billingTables. If that's not the case, migrate the database with IS_BILLING_ENABLED set to true:
npx nx run twenty-server:test:integration test/integration/billing/suites/billing-controller.integration-spec.ts
Doing: