-
-
Notifications
You must be signed in to change notification settings - Fork 739
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce new term licensed users (#8737)
Introducing new term Licensed users. Added query to read it from database and extensive tests to cover the logic.
- Loading branch information
Showing
8 changed files
with
120 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
src/lib/features/instance-stats/getLicensedUsers.e2e.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { | ||
createGetLicensedUsers, | ||
type GetLicensedUsers, | ||
} from './getLicensedUsers'; | ||
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; | ||
import getLogger from '../../../test/fixtures/no-logger'; | ||
|
||
let db: ITestDb; | ||
let getLicensedUsers: GetLicensedUsers; | ||
|
||
const mockUser = (deletedDaysAgo: number | null, uniqueId: number) => { | ||
const deletedAt = | ||
deletedDaysAgo !== null | ||
? new Date(Date.now() - deletedDaysAgo * 24 * 60 * 60 * 1000) | ||
: null; | ||
return { | ||
email: `${uniqueId}[email protected]`, | ||
email_hash: `${uniqueId}[email protected]`, | ||
deleted_at: deletedAt, | ||
}; | ||
}; | ||
|
||
beforeAll(async () => { | ||
db = await dbInit('licensed_users_serial', getLogger); | ||
getLicensedUsers = createGetLicensedUsers(db.rawDatabase); | ||
}); | ||
|
||
afterEach(async () => { | ||
await db.rawDatabase('users').delete(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await db.destroy(); | ||
}); | ||
|
||
test('should return 0 users when no users are present', async () => { | ||
await expect(getLicensedUsers()).resolves.toEqual(0); | ||
}); | ||
|
||
test('should return 1 active user with no deletion date', async () => { | ||
await db.rawDatabase('users').insert(mockUser(null, 1)); | ||
await expect(getLicensedUsers()).resolves.toEqual(1); | ||
}); | ||
|
||
test('should count user as active if deleted within 30 days', async () => { | ||
await db.rawDatabase('users').insert(mockUser(29, 2)); | ||
await expect(getLicensedUsers()).resolves.toEqual(1); | ||
}); | ||
|
||
test('should not count user as active if deleted more than 30 days ago', async () => { | ||
await db.rawDatabase('users').insert(mockUser(31, 3)); | ||
await expect(getLicensedUsers()).resolves.toEqual(0); | ||
}); | ||
|
||
test('should return correct count for multiple users with mixed deletion statuses', async () => { | ||
const users = [ | ||
...Array.from({ length: 10 }, (_, userId) => mockUser(null, userId)), // 10 active users | ||
...Array.from({ length: 5 }, (_, userId) => mockUser(29, userId + 10)), // 5 users deleted within 30 days | ||
...Array.from({ length: 3 }, (_, userId) => mockUser(31, userId + 15)), // 3 users deleted more than 30 days ago | ||
]; | ||
await db.rawDatabase('users').insert(users); | ||
await expect(getLicensedUsers()).resolves.toEqual(15); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { Db } from '../../server-impl'; | ||
|
||
export type GetLicensedUsers = () => Promise<number>; | ||
|
||
export const createGetLicensedUsers = | ||
(db: Db): GetLicensedUsers => | ||
async () => { | ||
const result = await db('users') | ||
.countDistinct('email_hash as activeCount') | ||
.whereNotNull('email_hash') | ||
.andWhere(function () { | ||
this.whereNull('deleted_at').orWhere( | ||
'deleted_at', | ||
'>=', | ||
db.raw("NOW() - INTERVAL '30 days'"), | ||
); | ||
}) | ||
.first(); | ||
|
||
return Number(result?.activeCount ?? 0); | ||
}; | ||
|
||
export const createFakeGetLicensedUsers = | ||
( | ||
licencedUsers: Awaited<ReturnType<GetLicensedUsers>> = 0, | ||
): GetLicensedUsers => | ||
() => | ||
Promise.resolve(licencedUsers); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters