Skip to content
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

Add Integration and unit tests on Billing #9317

Merged
merged 10 commits into from
Jan 9, 2025

Conversation

anamarn
Copy link
Contributor

@anamarn anamarn commented Jan 2, 2025

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:

  • Unit test on transformSubscriptionEventToSubscriptionItem
  • More tests cases in billingController integration tests.

@anamarn anamarn requested a review from FelixMalfait January 2, 2025 12:53
Copy link
Contributor

@greptile-apps greptile-apps bot left a 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

Comment on lines +7 to +11
it('should return true if metadata is empty', () => {
const metadata: Stripe.Metadata = {};

expect(isStripeValidProductMetadata(metadata)).toBe(true);
});
Copy link
Contributor

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.

Comment on lines +99 to +103
files: [
'*.spec.@(ts|tsx|js|jsx)',
'*.integration-spec.@(ts|tsx|js|jsx)',
'*.test.@(ts|tsx|js|jsx)',
],
Copy link
Contributor

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', () => {
Copy link
Contributor

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,
Copy link
Contributor

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

Comment on lines +38 to +39
mockData as any,
);
Copy link
Contributor

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')
Copy link
Contributor

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.

Comment on lines 34 to 44
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');
},
Copy link
Contributor

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.

Comment on lines 57 to 59
verify: (req: any, res, buf) => {
req.rawBody = buf;
},
Copy link
Contributor

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

describe('BillingController (integration)', () => {
const mockTimestamp = 1672531200;

//put this in utils?
Copy link
Contributor

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],
});
})
Copy link
Member

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(
Copy link
Member

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
Copy link
Member

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);
Copy link
Member

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

@@ -43,6 +43,8 @@ export class BillingController {
@Req() req: RawBodyRequest<Request>,
@Res() res: Response,
) {
let resultBody = {};
Copy link
Member

@Weiko Weiko Jan 3, 2025

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) {
Copy link
Member

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?

Copy link
Member

@Weiko Weiko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@anamarn anamarn changed the title DRAFT: Add Integration and unit tests on Billing Add Integration and unit tests on Billing Jan 9, 2025
@anamarn
Copy link
Contributor Author

anamarn commented Jan 9, 2025

New modifications

Due that some integrations tests requires IS_BILLING_ENABLED set to true, be mindful of the required environment variables that needs to be defined in this casse (look at EnvironmentService).

The migration separation implemented on 8772, implies that the billing tables are only created when billing is enabled. Thus I recommend as a first approach to run the integration tests with the flag 'with database reset'.

Always verify that the server is running correctly before running the tests.

Common tests (self-hosting instance)
Set IS_BILLING_ENABLED to false

 npx nx run twenty-server:test:integration:with-db-reset

Common+ Billing tests (cloud instance)
Set IS_BILLING_ENABLED to true
Add the other env variables required (you can also look at the server ci to know who they are)

npx nx run twenty-server:test:integration:with-db-reser test/integration/billing/suites/billing-controller.integration-spec.ts

You can ommit the file path if you want to run all tests

@@ -49,4 +49,6 @@
"files.associations": {
".cursorrules": "markdown"
},
"jestrunner.codeLensSelector": "**/*.{test,spec,integration-spec}.{js,jsx,ts,tsx}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is it?

@charlesBochet charlesBochet merged commit c39af5f into main Jan 9, 2025
20 checks passed
@charlesBochet charlesBochet deleted the feat/add-unit-integration-test-billing branch January 9, 2025 17:30
Copy link

github-actions bot commented Jan 9, 2025

Fails
🚫

node failed.

Log

�[31mError: �[39m SyntaxError: Unexpected token C in JSON at position 0
    at JSON.parse (<anonymous>)
�[90m    at parseJSONFromBytes (node:internal/deps/undici/undici:5584:19)�[39m
�[90m    at successSteps (node:internal/deps/undici/undici:5555:27)�[39m
�[90m    at fullyReadBody (node:internal/deps/undici/undici:1665:9)�[39m
�[90m    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m
�[90m    at async specConsumeBody (node:internal/deps/undici/undici:5564:7)�[39m
danger-results://tmp/danger-results-e909703a.json

Generated by 🚫 dangerJS against 2ef8786

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants