From 2e3a0063ba1c2cf2c8703de9cb85adf541c5c823 Mon Sep 17 00:00:00 2001
From: Darcy Ye <darcyye@silverhand.io>
Date: Mon, 5 Aug 2024 10:58:47 +0800
Subject: [PATCH] fix(core): should not throw when not adding any new roles to
 a user (#6387)

---
 packages/core/src/queries/users-roles.ts                 | 9 +++++++--
 packages/integration-tests/src/api/admin-user.ts         | 3 +++
 .../src/tests/api/oidc/id-token.test.ts                  | 4 +++-
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/packages/core/src/queries/users-roles.ts b/packages/core/src/queries/users-roles.ts
index 9b7d60606..10005dd87 100644
--- a/packages/core/src/queries/users-roles.ts
+++ b/packages/core/src/queries/users-roles.ts
@@ -42,14 +42,19 @@ export const createUsersRolesQueries = (pool: CommonQueryMethods) => {
       ${conditionalSql(limit, (value) => sql`limit ${value}`)}
     `);
 
-  const insertUsersRoles = async (usersRoles: CreateUsersRole[]) =>
-    pool.query(sql`
+  const insertUsersRoles = async (usersRoles: CreateUsersRole[]) => {
+    if (usersRoles.length === 0) {
+      return;
+    }
+
+    return pool.query(sql`
       insert into ${table} (${fields.id}, ${fields.userId}, ${fields.roleId}) values
       ${sql.join(
         usersRoles.map(({ id, userId, roleId }) => sql`(${id}, ${userId}, ${roleId})`),
         sql`, `
       )}
     `);
+  };
 
   const deleteUsersRolesByUserIdAndRoleId = async (userId: string, roleId: string) => {
     const { rowCount } = await pool.query(sql`
diff --git a/packages/integration-tests/src/api/admin-user.ts b/packages/integration-tests/src/api/admin-user.ts
index c8ca34b2e..7c9ad0360 100644
--- a/packages/integration-tests/src/api/admin-user.ts
+++ b/packages/integration-tests/src/api/admin-user.ts
@@ -76,6 +76,9 @@ export const deleteUserIdentity = async (userId: string, connectorTarget: string
 export const assignRolesToUser = async (userId: string, roleIds: string[]) =>
   authedAdminApi.post(`users/${userId}/roles`, { json: { roleIds } });
 
+export const putRolesToUser = async (userId: string, roleIds: string[]) =>
+  authedAdminApi.put(`users/${userId}/roles`, { json: { roleIds } });
+
 /**
  * Get roles assigned to the user.
  *
diff --git a/packages/integration-tests/src/tests/api/oidc/id-token.test.ts b/packages/integration-tests/src/tests/api/oidc/id-token.test.ts
index 89156eea1..a3ce8df5a 100644
--- a/packages/integration-tests/src/tests/api/oidc/id-token.test.ts
+++ b/packages/integration-tests/src/tests/api/oidc/id-token.test.ts
@@ -1,7 +1,7 @@
 import { Prompt } from '@logto/node';
 import { InteractionEvent, demoAppApplicationId } from '@logto/schemas';
 
-import { assignRolesToUser, putInteraction } from '#src/api/index.js';
+import { assignRolesToUser, putRolesToUser, putInteraction } from '#src/api/index.js';
 import { createRole } from '#src/api/role.js';
 import MockClient from '#src/client/index.js';
 import { demoAppRedirectUri } from '#src/constants.js';
@@ -56,6 +56,8 @@ describe('OpenID Connect ID token', () => {
   it('should be issued with correct `username` and `roles` claims', async () => {
     const role = await createRole({});
     await assignRolesToUser(userId, [role.id]);
+    // Should not throw when not adding any new role(s) to a user.
+    await expect(putRolesToUser(userId, [role.id])).resolves.not.toThrow();
     await fetchIdToken(['username', 'roles'], {
       username,
       roles: [role.name],