diff --git a/src/lib/features/users/user-updates-read-model.test.ts b/src/lib/features/users/user-updates-read-model.test.ts new file mode 100644 index 0000000000..f2e1b00a76 --- /dev/null +++ b/src/lib/features/users/user-updates-read-model.test.ts @@ -0,0 +1,65 @@ +import dbInit, { + type ITestDb, +} from '../../../test/e2e/helpers/database-init.js'; + +let db: ITestDb; +const TABLE = 'users'; +const INSERT_INSTANT = new Date(); +beforeAll(async () => { + db = await dbInit(); + + for (let i = 0; i < 10; i += 1) { + await db.rawDatabase(TABLE).insert({ + name: `test-user-${i}`, + username: `test-user-${i}`, + email: `test-user-${i}@example.com`, + created_at: INSERT_INSTANT, + updated_at: INSERT_INSTANT, + }); + } +}); + +test('returns all users if page is large enough', async () => { + const userUpdatesReadModel = db.stores.userUpdatesReadModel; + + const users = + await userUpdatesReadModel.getUsersUpdatedAfterOrEqual(INSERT_INSTANT); + expect(users).toHaveLength(10); +}); + +test('returns 0 if no users updated after timestamp', async () => { + const userUpdatesReadModel = db.stores.userUpdatesReadModel; + + const users = await userUpdatesReadModel.getUsersUpdatedAfterOrEqual( + new Date(INSERT_INSTANT.getTime() + 1), + ); + expect(users).toHaveLength(0); +}); + +test('returns the users in pages', async () => { + const userUpdatesReadModel = db.stores.userUpdatesReadModel; + const pageSize = 4; + + const usersPage1 = await userUpdatesReadModel.getUsersUpdatedAfterOrEqual( + INSERT_INSTANT, + pageSize, + ); + expect(usersPage1).toHaveLength(4); + expect(usersPage1[0].username).toBe('test-user-0'); + + const usersPage2 = await userUpdatesReadModel.getUsersUpdatedAfterOrEqual( + INSERT_INSTANT, + pageSize, + usersPage1[usersPage1.length - 1].id, + ); + expect(usersPage2).toHaveLength(4); + expect(usersPage2[0].username).toBe('test-user-4'); + + const usersPage3 = await userUpdatesReadModel.getUsersUpdatedAfterOrEqual( + INSERT_INSTANT, + pageSize, + usersPage2[usersPage2.length - 1].id, + ); + expect(usersPage3).toHaveLength(2); + expect(usersPage3[1].username).toBe('test-user-9'); +}); diff --git a/src/lib/features/users/user-updates-read-model.ts b/src/lib/features/users/user-updates-read-model.ts index 7191b60e35..4444b45e8c 100644 --- a/src/lib/features/users/user-updates-read-model.ts +++ b/src/lib/features/users/user-updates-read-model.ts @@ -50,9 +50,18 @@ export class UserUpdatesReadModel { return result ? result.last_updated_at : null; } + /** @deprecated */ async getUsersUpdatedAfter( date: Date, limit: number = 100, + ): Promise { + return this.getUsersUpdatedAfterOrEqual(date, limit, 0); + } + + async getUsersUpdatedAfterOrEqual( + date: Date, + limit: number = 100, + afterId: number = 0, ): Promise { const result = await this.db(USERS_TABLE) .where({ @@ -60,8 +69,15 @@ export class UserUpdatesReadModel { is_system: false, is_service: false, }) - .where('updated_at', '>', date) + .where((builder) => { + builder.where('updated_at', '>', date).orWhere((subBuilder) => { + subBuilder + .where('updated_at', '=', date) + .where('id', '>', afterId); + }); + }) .orderBy('updated_at', 'asc') + .orderBy('id', 'asc') .select([ ...USER_COLUMNS_PUBLIC, 'created_at',