const { test } = require("node:test"); const assert = require("node:assert"); const { fetchAccessToken, getUserProfile, updateUserProfile, findUserByAttribute, findUserByEmail, createUser, updateUser, deleteUser, getAllGroups, createGroup, addUserToGroup, getGroup, getGroupChildren, createSubGroup, updateGroup, getFederatedIdentities, linkFederatedIdentity, } = require("../src/lib/keycloakClient"); test("fetchAccessToken posts client_credentials grant and returns the access_token", async () => { let capturedUrl, capturedBody; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedBody = opts.body; return { ok: true, json: async () => ({ access_token: "fake-token" }) }; }; const token = await fetchAccessToken({ baseUrl: "https://kc.example.test", realm: "nexthd", clientId: "cid", clientSecret: "secret", fetchImpl, }); assert.strictEqual(token, "fake-token"); assert.strictEqual(capturedUrl, "https://kc.example.test/realms/nexthd/protocol/openid-connect/token"); assert.ok(capturedBody.includes("grant_type=client_credentials")); assert.ok(capturedBody.includes("client_id=cid")); }); test("getUserProfile returns the attributes array from /users/profile", async () => { const fetchImpl = async () => ({ ok: true, json: async () => ({ attributes: [{ name: "email" }] }) }); const profile = await getUserProfile({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", fetchImpl }); assert.strictEqual(profile.attributes[0].name, "email"); }); test("updateUserProfile PUTs the full profile object to /users/profile", async () => { let capturedUrl, capturedMethod, capturedBody; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedMethod = opts.method; capturedBody = JSON.parse(opts.body); return { ok: true }; }; await updateUserProfile({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", profile: { attributes: [{ name: "x" }] }, fetchImpl }); assert.strictEqual(capturedMethod, "PUT"); assert.ok(capturedUrl.endsWith("/users/profile")); assert.deepStrictEqual(capturedBody, { attributes: [{ name: "x" }] }); }); test("findUserByAttribute queries with q=attrName:attrValue and returns the first match or null", async () => { let capturedUrl; const fetchImpl = async (url) => { capturedUrl = url; return { ok: true, json: async () => (url.includes("hit") ? [{ id: "u1" }] : []) }; }; const found = await findUserByAttribute({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", attrName: "pleasanterResultId", attrValue: "hit-123", fetchImpl }); assert.strictEqual(found.id, "u1"); assert.ok(capturedUrl.includes("q=pleasanterResultId%3Ahit-123")); const notFound = await findUserByAttribute({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", attrName: "pleasanterResultId", attrValue: "miss-456", fetchImpl }); assert.strictEqual(notFound, null); }); test("findUserByEmail queries with email + exact=true and returns the first match or null", async () => { let capturedUrl; const fetchImpl = async (url) => { capturedUrl = url; return { ok: true, json: async () => [{ id: "u2" }] }; }; const found = await findUserByEmail({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", email: "taro@example.com", fetchImpl }); assert.strictEqual(found.id, "u2"); assert.ok(capturedUrl.includes("email=taro%40example.com&exact=true")); }); test("createUser posts to /users and returns the new id from the Location header", async () => { const fetchImpl = async () => ({ ok: true, headers: { get: (name) => (name.toLowerCase() === "location" ? "https://kc.example.test/admin/realms/nexthd/users/new-id-123" : null) }, }); const id = await createUser({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", user: { username: "u1" }, fetchImpl }); assert.strictEqual(id, "new-id-123"); }); test("updateUser puts the full user object to /users/{id}", async () => { let capturedUrl, capturedMethod, capturedBody; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedMethod = opts.method; capturedBody = JSON.parse(opts.body); return { ok: true }; }; await updateUser({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", userId: "u1", user: { username: "u1", email: "a@b.com", enabled: true }, fetchImpl }); assert.strictEqual(capturedMethod, "PUT"); assert.ok(capturedUrl.endsWith("/users/u1")); assert.deepStrictEqual(capturedBody, { username: "u1", email: "a@b.com", enabled: true }); }); test("deleteUser sends DELETE to /users/{id}", async () => { let capturedUrl, capturedMethod; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedMethod = opts.method; return { ok: true }; }; await deleteUser({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", userId: "u1", fetchImpl }); assert.strictEqual(capturedMethod, "DELETE"); assert.ok(capturedUrl.endsWith("/users/u1")); }); test("getAllGroups returns the group array from /groups", async () => { const fetchImpl = async () => ({ ok: true, json: async () => [{ id: "g1", name: "組織:情報システム部" }] }); const groups = await getAllGroups({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", fetchImpl }); assert.strictEqual(groups[0].name, "組織:情報システム部"); }); test("createGroup posts to /groups and returns the new id from the Location header", async () => { const fetchImpl = async () => ({ ok: true, headers: { get: (name) => (name.toLowerCase() === "location" ? "https://kc.example.test/admin/realms/nexthd/groups/g-new" : null) }, }); const id = await createGroup({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", name: "役職:課長", fetchImpl }); assert.strictEqual(id, "g-new"); }); test("createGroup includes attributes in the body when provided (org-unit hierarchy uses this for orgUnitId)", async () => { let capturedBody; const fetchImpl = async (url, opts) => { capturedBody = JSON.parse(opts.body); return { ok: true, headers: { get: () => "https://kc.example.test/admin/realms/nexthd/groups/g-new" } }; }; await createGroup({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", name: "組織", attributes: { orgUnitId: ["ou-1"] }, fetchImpl }); assert.deepStrictEqual(capturedBody, { name: "組織", attributes: { orgUnitId: ["ou-1"] } }); }); test("getGroup GETs /groups/{id} and returns the group object", async () => { let capturedUrl; const fetchImpl = async (url) => { capturedUrl = url; return { ok: true, json: async () => ({ id: "g1", name: "西東京建設", attributes: { orgUnitId: ["ou-1"] } }) }; }; const group = await getGroup({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", groupId: "g1", fetchImpl }); assert.ok(capturedUrl.endsWith("/groups/g1")); assert.strictEqual(group.name, "西東京建設"); }); test("getGroupChildren GETs /groups/{id}/children and returns the child group array", async () => { let capturedUrl; const fetchImpl = async (url) => { capturedUrl = url; return { ok: true, json: async () => [{ id: "g2", name: "営業設計部" }] }; }; const children = await getGroupChildren({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", groupId: "g1", fetchImpl }); assert.ok(capturedUrl.endsWith("/groups/g1/children?max=1000")); assert.strictEqual(children[0].name, "営業設計部"); }); test("createSubGroup posts to /groups/{parentId}/children with attributes and returns the new id", async () => { let capturedUrl, capturedBody; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedBody = JSON.parse(opts.body); return { ok: true, headers: { get: () => "https://kc.example.test/admin/realms/nexthd/groups/g-child" } }; }; const id = await createSubGroup({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", parentId: "g1", name: "営業設計部", attributes: { orgUnitId: ["ou-2"] }, fetchImpl, }); assert.ok(capturedUrl.endsWith("/groups/g1/children")); assert.deepStrictEqual(capturedBody, { name: "営業設計部", attributes: { orgUnitId: ["ou-2"] } }); assert.strictEqual(id, "g-child"); }); test("updateGroup PUTs the full group object to /groups/{id}", async () => { let capturedUrl, capturedMethod, capturedBody; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedMethod = opts.method; capturedBody = JSON.parse(opts.body); return { ok: true }; }; await updateGroup({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", groupId: "g1", group: { name: "西東京建設(改称)", attributes: { orgUnitId: ["ou-1"] } }, fetchImpl }); assert.strictEqual(capturedMethod, "PUT"); assert.ok(capturedUrl.endsWith("/groups/g1")); assert.deepStrictEqual(capturedBody, { name: "西東京建設(改称)", attributes: { orgUnitId: ["ou-1"] } }); }); test("addUserToGroup PUTs to /users/{userId}/groups/{groupId} with no body", async () => { let capturedUrl, capturedMethod; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedMethod = opts.method; return { ok: true }; }; await addUserToGroup({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", userId: "u1", groupId: "g1", fetchImpl }); assert.strictEqual(capturedMethod, "PUT"); assert.ok(capturedUrl.endsWith("/users/u1/groups/g1")); }); test("callApi throws with status and body text when the response is not ok", async () => { const fetchImpl = async () => ({ ok: false, status: 403, text: async () => "Forbidden" }); await assert.rejects(() => getAllGroups({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", fetchImpl }), /403/); }); test("getFederatedIdentities returns the federated-identity array for a user", async () => { const fetchImpl = async () => ({ ok: true, json: async () => [{ identityProvider: "lineworks", userId: "taro@next-hd.co.jp" }] }); const identities = await getFederatedIdentities({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", userId: "u1", fetchImpl }); assert.strictEqual(identities[0].identityProvider, "lineworks"); }); test("linkFederatedIdentity POSTs identityProvider/userId/userName to /users/{userId}/federated-identity/{provider}", async () => { let capturedUrl, capturedMethod, capturedBody; const fetchImpl = async (url, opts) => { capturedUrl = url; capturedMethod = opts.method; capturedBody = JSON.parse(opts.body); return { ok: true }; }; await linkFederatedIdentity({ baseUrl: "https://kc.example.test", realm: "nexthd", accessToken: "t", userId: "u1", provider: "lineworks", identityUserId: "taro@next-hd.co.jp", fetchImpl, }); assert.strictEqual(capturedMethod, "POST"); assert.ok(capturedUrl.endsWith("/users/u1/federated-identity/lineworks")); assert.deepStrictEqual(capturedBody, { identityProvider: "lineworks", userId: "taro@next-hd.co.jp", userName: "taro@next-hd.co.jp" }); });