diff --git a/.changeset/tender-goats-cross.md b/.changeset/tender-goats-cross.md new file mode 100644 index 0000000000000..d7204dbcdbe5d --- /dev/null +++ b/.changeset/tender-goats-cross.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixes an issue with airgapped restrictions ignoring the warning period. diff --git a/apps/meteor/jest.config.ts b/apps/meteor/jest.config.ts index 485dd20b77b57..677c3a06d28cd 100644 --- a/apps/meteor/jest.config.ts +++ b/apps/meteor/jest.config.ts @@ -38,6 +38,7 @@ export default { '/app/cloud/server/functions/supportedVersionsToken/**.spec.ts', '/app/utils/lib/**.spec.ts', '/server/lib/auditServerEvents/**.spec.ts', + '/server/cron/**.spec.ts', '/app/api/server/**.spec.ts', '/app/api/server/middlewares/**.spec.ts', ], diff --git a/apps/meteor/server/cron/usageReport.spec.ts b/apps/meteor/server/cron/usageReport.spec.ts new file mode 100644 index 0000000000000..1b4b464bfbbd6 --- /dev/null +++ b/apps/meteor/server/cron/usageReport.spec.ts @@ -0,0 +1,53 @@ +import { AirGappedRestriction } from '@rocket.chat/license'; +import { Statistics } from '@rocket.chat/models'; + +import { sendUsageReportAndComputeRestriction } from './usageReport'; + +jest.mock('@rocket.chat/license', () => ({ + AirGappedRestriction: { + computeRestriction: jest.fn(), + }, +})); + +jest.mock('@rocket.chat/models', () => ({ + Statistics: { + findLastStatsToken: jest.fn(), + }, +})); + +jest.mock('../../app/statistics/server/functions/sendUsageReport', () => ({ + sendUsageReport: () => undefined, +})); + +describe('sendUsageReportAndComputeRestriction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should pass statsToken to computeRestriction when provided', async () => { + const mockStatsToken = 'test-token'; + await sendUsageReportAndComputeRestriction(mockStatsToken); + + expect(AirGappedRestriction.computeRestriction).toHaveBeenCalledWith(mockStatsToken); + expect(Statistics.findLastStatsToken).not.toHaveBeenCalled(); + }); + + it('should use findLastStatsToken result when statsToken is omitted', async () => { + const mockLastToken = 'last-token'; + (Statistics.findLastStatsToken as jest.Mock).mockResolvedValue(mockLastToken); + + await sendUsageReportAndComputeRestriction(); + + expect(Statistics.findLastStatsToken).toHaveBeenCalled(); + expect(AirGappedRestriction.computeRestriction).toHaveBeenCalledWith(mockLastToken); + }); + + it('should pass undefined to computeRestriction when both statsToken is omitted and findLastStatsToken returns undefined', async () => { + (Statistics.findLastStatsToken as jest.Mock).mockResolvedValue(undefined); + + await sendUsageReportAndComputeRestriction(); + + expect(Statistics.findLastStatsToken).toHaveBeenCalled(); + expect(AirGappedRestriction.computeRestriction).toHaveBeenCalledWith(undefined); + }); +}); diff --git a/apps/meteor/server/cron/usageReport.ts b/apps/meteor/server/cron/usageReport.ts index 79f8340671b04..363ea4f308c77 100644 --- a/apps/meteor/server/cron/usageReport.ts +++ b/apps/meteor/server/cron/usageReport.ts @@ -1,18 +1,28 @@ import { cronJobs } from '@rocket.chat/cron'; import { AirGappedRestriction } from '@rocket.chat/license'; import type { Logger } from '@rocket.chat/logger'; +import { Statistics } from '@rocket.chat/models'; import { sendUsageReport } from '../../app/statistics/server/functions/sendUsageReport'; +export const sendUsageReportAndComputeRestriction = async (statsToken?: string) => { + // If the report failed to be sent we need to get the last existing token + // to ensure that the restriction respects the warning period. + // If no token is passed, the workspace will be instantly restricted. + const token = statsToken || (await Statistics.findLastStatsToken()); + void AirGappedRestriction.computeRestriction(token); +}; + export async function usageReportCron(logger: Logger): Promise { const name = 'Generate and save statistics'; + const statsToken = await sendUsageReport(logger); - void AirGappedRestriction.computeRestriction(statsToken); + await sendUsageReportAndComputeRestriction(statsToken); const now = new Date(); return cronJobs.add(name, `12 ${now.getHours()} * * *`, async () => { const statsToken = await sendUsageReport(logger); - void AirGappedRestriction.computeRestriction(statsToken); + await sendUsageReportAndComputeRestriction(statsToken); }); }