⚡
Blitz.jsで認証状態付きAPI Routesをテストする【暴力】
※ 暴力の実装です。厳密なBlitz.js内部仕様の考証は行っておりません
Sessionデータをモックする
この処理の前にユーザーのモックが必要ですが、 db.user.create()
するだけなので割愛します。
spec/mocks/session.ts
import {
EmptyPublicData,
generateToken,
hash256,
PublicData,
TOKEN_SEPARATOR,
} from "@blitzjs/auth"
export async function createSession(params: { userId: number }) {
const handle = generateToken()
// ctx.session.$publicData とかに詰まってるやつ
const publicDataString = JSON.stringify({
userId: params.userId,
role: "USER"
})
// SessionTokenを生成する
// SEE: https://github.com/blitz-js/blitz/blob/6f44c2334ee3665b450cea6c873d3500249b3e49/packages/blitz-auth/src/server/auth-sessions.ts#L445
const sessionToken = Buffer.from(
[handle, generateToken(32), hash256(publicDataString), "v0"].join(TOKEN_SEPARATOR)
).toString("base64")
const antiCSRFToken = generateToken()
await db.session.create({
data: {
userId: params.userId,
hashedSessionToken: hash256(sessionToken),
antiCSRFToken,
publicData: publicDataString,
expiresAt: null,
handle,
},
})
return { sessionToken, antiCSRFToken }
}
API Routesのテストを書く
API Routesのテストには next-test-api-route-handler
を利用します。
型エラーが出ますが、各自""よしなに""してあげてください
mockApiSession
がAPI Routesのハンドラ実行前に認証状態をモックする関数です
pages/api/__test__/session.test.ts
import db from "db"
import { testApiHandler } from "next-test-api-route-handler"
import { createFakeUser } from "@/spec/mocks/user"
import { createFakeSession, mockApiSession } from "@/spec/mocks/session"
import { mockApiSession } from "@/spec/helper/session"
beforeEach(async () => {
await db.$reset()
})
describe("/api/session", () => {
it("should return { user: null } if not authorized", async () => {
await testApiHandler({
pagesHandler: mockApiSession({ sessionToken: null }, sessionHandler),
async test({ fetch }) {
const res = await fetch({
method: "GET",
})
const data = await res.json()
expect(res.status).toBe(200)
expect(data).toEqual({ user: null })
},
})
})
it("should return user data if authorized", async () => {
const user = await createFakeUser()
const session = await createFakeSession({ userId: user.id })
await testApiHandler({
pagesHandler: mockApiSession({ sessionToken: session.sessionToken }, sessionHandler),
async test({ fetch }) {
const res = await fetch({
method: "GET",
})
const data = await res.json()
expect(res.status).toBe(200)
expect(data).toMatchObject({ user: { id: 1, isPremium: true } })
},
})
})
})
いざ! mockApiSession関数で認証状態モック!!
req.cookieの${__BLITZ_SESSION_COOKIE_PREFIX}_sSessionToken
にSessionTokenなどを設定するだけです
spec/helper/session.ts
export function mockApiSession(
{
sessionToken,
}: {
sessionToken: string | null
},
handler: (req: NextApiRequest, res: BlitzNextApiResponse, ctx: any) => void | Promise<void>
) {
return async (req: NextApiRequest, res: BlitzNextApiResponse) => {
// SEE: https://github.com/blitz-js/blitz/blob/main/packages/blitz-auth/src/shared/constants.ts#L7
const cookieSessionTokenName = `${__BLITZ_SESSION_COOKIE_PREFIX}_sSessionToken`
const cooliePublicDataTokenName = `${__BLITZ_SESSION_COOKIE_PREFIX}_sPublicDataToken`
if (!sessionToken) {
delete req.cookies[cookieSessionTokenName]
delete req.cookies[cooliePublicDataTokenName]
return
} else {
req.cookies[cookieSessionTokenName] = sessionToken
const { handle, id, hashedPublicData, version } = parseSessionToken(sessionToken)
const persistedSession = await global.sessionConfig.getSession(handle)
if (persistedSession?.publicData) {
req.cookies[cooliePublicDataTokenName] = createPublicDataToken(persistedSession.publicData)
}
}
await handler(req, res, res.blitzCtx)
// SEE: https://github.com/blitz-js/blitz/blob/main/packages/blitz-auth/src/server/auth-sessions.ts#L452
function parseSessionToken(token: string) {
const [handle, id, hashedPublicData, version] = Buffer.from(token, "base64")
.toString()
.split(TOKEN_SEPARATOR)
if (!handle || !id || !hashedPublicData || !version) {
throw new Error("Failed to parse session token")
}
return {
handle,
id,
hashedPublicData,
version,
}
}
function createPublicDataToken(publicData: string | PublicData | EmptyPublicData) {
const payload = typeof publicData === "string" ? publicData : JSON.stringify(publicData)
return Buffer.from(payload).toString("base64")
}
}
}
@blitzjs/auth
の中をあれこれコードを読んだりパクったりして実装をコピーしていたら、
「${__BLITZ_SESSION_COOKIE_PREFIX}_sSessionToken`設定するだけでよかったやんけ!!」と気づいてしまい、特になにもしていないコードになりました。
ウケるな🙂
これで認証状態付きのAPI Routesのテストができるようになりましたが、この記事は「テスト動いた❗みんな見てみて❗❗」の飛ばし記事なので、この後地獄を見るかもしれません。
お達者で!!!!
はなくら より
Discussion