Skip to content

Commit fe90cb5

Browse files
committedFeb 8, 2025
feat: includes getRemainingQuota method and your tests
1 parent 0733fa9 commit fe90cb5

File tree

5 files changed

+176
-2
lines changed

5 files changed

+176
-2
lines changed
 

‎apps/backend/src/services/auth/permissions/permissions.service.ts

+26
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,30 @@ export class PermissionsService {
173173
item.constructor,
174174
});
175175
}
176+
177+
async getRemainingQuota(orgId: string, section: Sections): Promise<number> {
178+
if (section === Sections.CHANNEL) {
179+
const { subscription, options } = await this.getPackageOptions(orgId);
180+
const integrations = await this._integrationService.getIntegrationsList(orgId);
181+
const validChannels = integrations.filter(integration => !integration.refreshNeeded).length;
182+
let limit = options.channel;
183+
if (subscription && subscription.totalChannels > limit) {
184+
limit = subscription.totalChannels;
185+
}
186+
return limit - validChannels;
187+
} else if (section === Sections.WEBHOOKS) {
188+
const { options } = await this.getPackageOptions(orgId);
189+
const totalWebhooks = await this._webhooksService.getTotal(orgId);
190+
return options.webhooks - totalWebhooks;
191+
} else if (section === Sections.POSTS_PER_MONTH) {
192+
const { options } = await this.getPackageOptions(orgId);
193+
const subscriptionInfo = await this._subscriptionService.getSubscription(orgId);
194+
const createdAt = subscriptionInfo?.createdAt || new Date();
195+
const totalMonthsPassed = Math.abs(dayjs(createdAt).diff(dayjs(), 'month'));
196+
const checkFrom = dayjs(createdAt).add(totalMonthsPassed, 'month');
197+
const count = await this._postsService.countPostsFromDay(orgId, checkFrom.toDate());
198+
return options.posts_per_month - count;
199+
}
200+
return 0;
201+
}
176202
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { mock } from 'jest-mock-extended';
2+
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
3+
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
4+
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
5+
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
6+
import { PermissionsService } from './permissions.service';
7+
import { Sections } from './permissions.service';
8+
import { Period, SubscriptionTier } from '@prisma/client';
9+
10+
// Mock of dependent services
11+
const mockSubscriptionService = mock<SubscriptionService>();
12+
const mockPostsService = mock<PostsService>();
13+
const mockIntegrationService = mock<IntegrationService>();
14+
const mockWebHookService = mock<WebhooksService>();
15+
16+
describe('PermissionsService', () => {
17+
let service: PermissionsService;
18+
19+
// Initial setup before each test
20+
beforeEach(() => {
21+
process.env.STRIPE_PUBLISHABLE_KEY = 'mock_stripe_key';
22+
service = new PermissionsService(
23+
mockSubscriptionService,
24+
mockPostsService,
25+
mockIntegrationService,
26+
mockWebHookService
27+
);
28+
});
29+
30+
// Reusable mocks for `getPackageOptions`
31+
const baseSubscription = {
32+
id: 'mock-id',
33+
organizationId: 'mock-org-id',
34+
subscriptionTier: 'PRO' as SubscriptionTier,
35+
identifier: 'mock-identifier',
36+
cancelAt: new Date(),
37+
period: {} as Period,
38+
totalChannels: 5,
39+
isLifetime: false,
40+
createdAt: new Date(),
41+
updatedAt: new Date(),
42+
deletedAt: null,
43+
disabled: false,
44+
tokenExpiration: new Date(),
45+
profile: 'mock-profile',
46+
postingTimes: '[]',
47+
lastPostedAt: new Date(),
48+
};
49+
50+
const baseOptions = {
51+
channel: 10,
52+
current: 'mock-current',
53+
month_price: 20,
54+
year_price: 200,
55+
posts_per_month: 100,
56+
team_members: true,
57+
community_features: true,
58+
featured_by_gitroom: true,
59+
ai: true,
60+
import_from_channels: true,
61+
image_generator: false,
62+
image_generation_count: 50,
63+
public_api: true,
64+
webhooks: 10
65+
};
66+
67+
const baseIntegration = {
68+
id: 'mock-integration-id',
69+
organizationId: 'mock-org-id',
70+
createdAt: new Date(),
71+
updatedAt: new Date(),
72+
deletedAt: new Date(),
73+
additionalSettings: '{}',
74+
refreshNeeded: false,
75+
refreshToken: 'mock-refresh-token',
76+
name: 'Mock Integration',
77+
internalId: 'mock-internal-id',
78+
picture: 'mock-picture-url',
79+
providerIdentifier: 'mock-provider',
80+
token: 'mock-token',
81+
type: 'social',
82+
inBetweenSteps: false,
83+
disabled: false,
84+
tokenExpiration: new Date(),
85+
profile: 'mock-profile',
86+
postingTimes: '[]',
87+
lastPostedAt: new Date(),
88+
customInstanceDetails: 'mock-details',
89+
customerId: 'mock-customer-id',
90+
rootInternalId: 'mock-root-id',
91+
customer: {
92+
id: 'mock-customer-id',
93+
createdAt: new Date(),
94+
updatedAt: new Date(),
95+
deletedAt: new Date(),
96+
name: 'Mock Customer',
97+
orgId: 'mock-org-id',
98+
},
99+
};
100+
101+
describe('getRemainingQuota()', () => {
102+
it('should return 0 for unsupported section (default case)', async () => {
103+
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
104+
subscription: baseSubscription,
105+
options: baseOptions,
106+
});
107+
const remaining = await service.getRemainingQuota('mock-org-id', Sections.ADMIN);
108+
expect(remaining).toBe(0);
109+
});
110+
111+
it('should return remaining channels quota for SECTION.CHANNEL', async () => {
112+
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
113+
subscription: baseSubscription,
114+
options: baseOptions,
115+
});
116+
mockIntegrationService.getIntegrationsList.mockResolvedValue([
117+
{ ...baseIntegration, refreshNeeded: false },
118+
{ ...baseIntegration, refreshNeeded: false },
119+
{ ...baseIntegration, refreshNeeded: true },
120+
]);
121+
const remaining = await service.getRemainingQuota('mock-org-id', Sections.CHANNEL);
122+
expect(remaining).toBe(8);
123+
});
124+
it('should return remaining webhooks quota for SECTION.WEBHOOKS', async () => {
125+
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
126+
subscription: baseSubscription,
127+
options: baseOptions,
128+
});
129+
mockWebHookService.getTotal.mockResolvedValue(2);
130+
const remaining = await service.getRemainingQuota('mock-org-id', Sections.WEBHOOKS);
131+
expect(remaining).toBe(8);
132+
});
133+
it('should return remaining posts quota for SECTION.POSTS_PER_MONTH', async () => {
134+
const fixedDate = new Date('2023-01-01T00:00:00Z');
135+
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
136+
subscription: baseSubscription,
137+
options: baseOptions,
138+
});
139+
mockSubscriptionService.getSubscription.mockResolvedValue({
140+
...baseSubscription,
141+
createdAt: fixedDate,
142+
});
143+
mockPostsService.countPostsFromDay.mockResolvedValue(30);
144+
const remaining = await service.getRemainingQuota('mock-org-id', Sections.POSTS_PER_MONTH);
145+
expect(remaining).toBe(70);
146+
});
147+
})
148+
});

‎apps/backend/tsconfig.spec.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
"src/**/*.test.ts",
1111
"src/**/*.spec.ts",
1212
"src/**/*.d.ts"
13-
]
13+
, "src/services/auth/permissions/check-permissions.service.spec.ts" ]
1414
}

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"docker-build": "./var/docker/docker-build.sh",
3131
"docker-create": "./var/docker/docker-create.sh",
3232
"postinstall": "npm run update-plugins && npm run prisma-generate",
33-
"test": "jest --coverage"
33+
"test": "jest"
3434
},
3535
"private": true,
3636
"dependencies": {

0 commit comments

Comments
 (0)