* refactor: allow non unique PlatformBilling customerId * feat: add PlatformBilling managed organizations fields * feat: ManagedOrganization table * refactor: bill overdue based on teamId * refactor: restructure organizations module * wip: organizations endpoints * Revert "wip: organizations endpoints" This reverts commit 0e9e66fc74f31da436f12930c1b6597c650ed621. * refactor: unique index for managed organizations * wip: organizations endpoints * wip: create managed organization * remove unecessary membership check because we have guard * feat: create managed org * feat: get managed org and orgs * feat: update and delete managed orgs * wip: api key logic * feat: allow variable api key length * feat: refresh managed org api key * feat: create managed org OAuth clients using api key * finish merge main * chore: bump platform libraries * tests: fix and add more tests to api-auth.strategy.e2e * refactor: dont request managed org slug and handle metadata as object * revert: billing service and repository update overdue based on sub and customer ids * refactor: v2 OAuth client permissions (#19501) * refactor: v2 permissions as string array * refactor: frontend work with permissions as string * tests: test '*' permissions * fix:managed org creator have profile & test can fetch oauth client * fix: tests * fix: OAuthClientCard on frontend * fix: tests
214 lines
7.3 KiB
TypeScript
214 lines
7.3 KiB
TypeScript
import { bootstrap } from "@/app";
|
|
import { AppModule } from "@/app.module";
|
|
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
|
import { StripeService } from "@/modules/stripe/stripe.service";
|
|
import { TokensModule } from "@/modules/tokens/tokens.module";
|
|
import { UsersModule } from "@/modules/users/users.module";
|
|
import { UserWithProfile } from "@/modules/users/users.repository";
|
|
import { INestApplication } from "@nestjs/common";
|
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
|
import { Test } from "@nestjs/testing";
|
|
import Stripe from "stripe";
|
|
import * as request from "supertest";
|
|
import { PlatformBillingRepositoryFixture } from "test/fixtures/repository/billing.repository.fixture";
|
|
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
|
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
|
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
|
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
|
import { randomString } from "test/utils/randomString";
|
|
import { withApiAuth } from "test/utils/withApiAuth";
|
|
|
|
import { Team, PlatformOAuthClient, PlatformBilling } from "@calcom/prisma/client";
|
|
|
|
describe("Platform Billing Controller (e2e)", () => {
|
|
let app: INestApplication;
|
|
const userEmail = `billing-user-${randomString()}@api.com`;
|
|
let user: UserWithProfile;
|
|
let billing: PlatformBilling;
|
|
let userRepositoryFixture: UserRepositoryFixture;
|
|
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
|
let profileRepositoryFixture: ProfileRepositoryFixture;
|
|
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
|
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
|
let oAuthClient: PlatformOAuthClient;
|
|
let platformBillingRepositoryFixture: PlatformBillingRepositoryFixture;
|
|
let organization: Team;
|
|
|
|
beforeAll(async () => {
|
|
const moduleRef = await withApiAuth(
|
|
userEmail,
|
|
Test.createTestingModule({
|
|
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
|
})
|
|
).compile();
|
|
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
|
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
|
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
|
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
|
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
|
platformBillingRepositoryFixture = new PlatformBillingRepositoryFixture(moduleRef);
|
|
organization = await organizationsRepositoryFixture.create({
|
|
name: `billing-organization-${randomString()}`,
|
|
});
|
|
|
|
user = await userRepositoryFixture.create({
|
|
email: userEmail,
|
|
username: userEmail,
|
|
});
|
|
|
|
await profileRepositoryFixture.create({
|
|
uid: `usr-${user.id}`,
|
|
username: userEmail,
|
|
organization: {
|
|
connect: {
|
|
id: organization.id,
|
|
},
|
|
},
|
|
user: {
|
|
connect: {
|
|
id: user.id,
|
|
},
|
|
},
|
|
});
|
|
await membershipsRepositoryFixture.create({
|
|
role: "OWNER",
|
|
team: { connect: { id: organization.id } },
|
|
user: { connect: { id: user.id } },
|
|
accepted: true,
|
|
});
|
|
|
|
billing = await platformBillingRepositoryFixture.create(organization.id);
|
|
|
|
app = moduleRef.createNestApplication();
|
|
bootstrap(app as NestExpressApplication);
|
|
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
userRepositoryFixture.deleteByEmail(user.email);
|
|
await app.close();
|
|
});
|
|
|
|
it("/billing/webhook (POST) should set billing free plan for org", () => {
|
|
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
|
|
() =>
|
|
({
|
|
webhooks: {
|
|
constructEventAsync: async () => {
|
|
return {
|
|
type: "checkout.session.completed",
|
|
data: {
|
|
object: {
|
|
metadata: {
|
|
teamId: organization.id,
|
|
plan: "FREE",
|
|
},
|
|
mode: "subscription",
|
|
},
|
|
},
|
|
};
|
|
},
|
|
},
|
|
} as unknown as Stripe)
|
|
);
|
|
|
|
return request(app.getHttpServer())
|
|
.post("/v2/billing/webhook")
|
|
.expect(200)
|
|
.then(async (res) => {
|
|
const billing = await platformBillingRepositoryFixture.get(organization.id);
|
|
expect(billing?.plan).toEqual("FREE");
|
|
});
|
|
});
|
|
it("/billing/webhook (POST) failed payment should set billing free plan to overdue", () => {
|
|
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
|
|
() =>
|
|
({
|
|
webhooks: {
|
|
constructEventAsync: async () => {
|
|
return {
|
|
type: "invoice.payment_failed",
|
|
data: {
|
|
object: {
|
|
customer: billing?.customerId,
|
|
subscription: billing?.subscriptionId,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
},
|
|
} as unknown as Stripe)
|
|
);
|
|
return request(app.getHttpServer())
|
|
.post("/v2/billing/webhook")
|
|
.expect(200)
|
|
.then(async (res) => {
|
|
const billing = await platformBillingRepositoryFixture.get(organization.id);
|
|
expect(billing?.overdue).toEqual(true);
|
|
});
|
|
});
|
|
|
|
it("/billing/webhook (POST) success payment should set billing free plan to not overdue", () => {
|
|
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
|
|
() =>
|
|
({
|
|
webhooks: {
|
|
constructEventAsync: async () => {
|
|
return {
|
|
type: "invoice.payment_succeeded",
|
|
data: {
|
|
object: {
|
|
customer: billing?.customerId,
|
|
subscription: billing?.subscriptionId,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
},
|
|
} as unknown as Stripe)
|
|
);
|
|
|
|
return request(app.getHttpServer())
|
|
.post("/v2/billing/webhook")
|
|
.expect(200)
|
|
.then(async (res) => {
|
|
const billing = await platformBillingRepositoryFixture.get(organization.id);
|
|
expect(billing?.overdue).toEqual(false);
|
|
});
|
|
});
|
|
|
|
it("/billing/webhook (POST) should delete subscription", () => {
|
|
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
|
|
() =>
|
|
({
|
|
webhooks: {
|
|
constructEventAsync: async () => {
|
|
return {
|
|
type: "customer.subscription.deleted",
|
|
data: {
|
|
object: {
|
|
metadata: {
|
|
teamId: organization.id,
|
|
plan: "FREE",
|
|
},
|
|
id: billing?.subscriptionId,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
},
|
|
} as unknown as Stripe)
|
|
);
|
|
|
|
return request(app.getHttpServer())
|
|
.post("/v2/billing/webhook")
|
|
.expect(200)
|
|
.then(async (res) => {
|
|
const billing = await platformBillingRepositoryFixture.get(organization.id);
|
|
expect(billing).toBeNull();
|
|
});
|
|
});
|
|
});
|