GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。 NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは 別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
69 lines
3.2 KiB
JavaScript
69 lines
3.2 KiB
JavaScript
const { test } = require("node:test");
|
|
const assert = require("node:assert");
|
|
const crypto = require("node:crypto");
|
|
const { encrypt } = require("../src/lib/tokenCrypto");
|
|
const { getValidAccessToken } = require("../src/lib/tokenService");
|
|
|
|
const KEY_HEX = crypto.randomBytes(32).toString("hex");
|
|
const PLEASANTER = { baseUrl: "https://example.test/", apiKey: "k", siteId: 1, encryptionKey: KEY_HEX };
|
|
const OAUTH = { clientId: "cid", clientSecret: "secret" };
|
|
|
|
function itemResponse({ expiresAt, accessToken = "access-current", refreshToken = "refresh-current" }) {
|
|
return {
|
|
ok: true,
|
|
json: async () => ({
|
|
StatusCode: 200,
|
|
Response: {
|
|
Data: [{
|
|
ResultId: 5,
|
|
ClassHash: { ClassA: "lineworks-form-sync", Class001: "svc@example.co.jp", Class002: "form form.read" },
|
|
DescriptionHash: { Description001: encrypt(accessToken, KEY_HEX), Description002: encrypt(refreshToken, KEY_HEX) },
|
|
DateHash: { Date001: expiresAt },
|
|
}],
|
|
TotalCount: 1,
|
|
},
|
|
}),
|
|
};
|
|
}
|
|
|
|
test("returns the stored access token as-is when it is still valid (no refresh call made)", async () => {
|
|
const now = () => new Date("2026-08-24T00:30:00.000Z");
|
|
let refreshCalled = false;
|
|
const fetchImpl = async (url) => {
|
|
if (url.endsWith("/get")) return itemResponse({ expiresAt: "2026-08-24T01:00:00.000Z" });
|
|
if (url.includes("auth.worksmobile.com")) { refreshCalled = true; }
|
|
if (url.endsWith("/update")) return { ok: true, json: async () => ({ Id: 5, StatusCode: 200 }) };
|
|
throw new Error(`unexpected url ${url}`);
|
|
};
|
|
const token = await getValidAccessToken({ key: "lineworks-form-sync", pleasanter: PLEASANTER, oauth: OAUTH, fetchImpl, now });
|
|
assert.strictEqual(token, "access-current");
|
|
assert.strictEqual(refreshCalled, false);
|
|
});
|
|
|
|
test("refreshes and persists a new token when the stored one has expired", async () => {
|
|
const now = () => new Date("2026-08-24T02:00:00.000Z");
|
|
let updateBody;
|
|
const fetchImpl = async (url, opts) => {
|
|
if (url.endsWith("/get")) return itemResponse({ expiresAt: "2026-08-24T01:00:00.000Z" });
|
|
if (url.includes("auth.worksmobile.com")) {
|
|
return { ok: true, json: async () => ({ access_token: "access-new", refresh_token: "refresh-new", expires_in: 3600 }) };
|
|
}
|
|
if (url.endsWith("/update")) { updateBody = JSON.parse(opts.body); return { ok: true, json: async () => ({ Id: 5, StatusCode: 200 }) }; }
|
|
throw new Error(`unexpected url ${url}`);
|
|
};
|
|
const token = await getValidAccessToken({ key: "lineworks-form-sync", pleasanter: PLEASANTER, oauth: OAUTH, fetchImpl, now });
|
|
assert.strictEqual(token, "access-new");
|
|
assert.strictEqual(updateBody.DateHash.Date001, "2026-08-24T03:00:00.000Z");
|
|
});
|
|
|
|
test("throws a descriptive error when the key is not registered", async () => {
|
|
const fetchImpl = async (url) => {
|
|
if (url.endsWith("/get")) return { ok: true, json: async () => ({ StatusCode: 200, Response: { Data: [], TotalCount: 0 } }) };
|
|
throw new Error(`unexpected url ${url}`);
|
|
};
|
|
await assert.rejects(
|
|
() => getValidAccessToken({ key: "unknown-key", pleasanter: PLEASANTER, oauth: OAUTH, fetchImpl, now: () => new Date() }),
|
|
/unknown-key/
|
|
);
|
|
});
|