先日、Super なんとかって記事を書いたんですが、その最後に書いた「ページの中からトークンを取得して、それを含めてリクエスト。なんてのを試したい~~」をやってみます。
参考
実装
テスト対象のアプリケーション
テスト対象のアプリケーションは次の通りです。
GET /form
にアクセスし、返ってきたフォームを送ると /
に飛ばされるという動きです。
server.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import { Application, type Context, Router, } from "https://deno.land/x/oak@v10.4.0/mod.ts"; import { computeAesGcmTokenPair, computeVerifyAesGcmTokenPair, } from "https://deno.land/x/deno_csrf@0.0.4/mod.ts";
const CRYPT_KEY = Deno.env.get("CRYPT_KEY") as string; const router = new Router();
router.post("/form", async ({ request, response, cookies }: Context) => { const form = await request.body({ type: "form" }).value; const csrfToken = form.get("csrf_token") || "";
const csrfCookie = (await cookies.get("csrf_cookie")) || "";
const result = computeVerifyAesGcmTokenPair(CRYPT_KEY, csrfToken, csrfCookie);
if (!result) { return response.redirect("/form"); }
response.redirect("/"); });
router.get("/form", async ({ response, cookies }: Context) => { const pair = computeAesGcmTokenPair(CRYPT_KEY, 123); pair.tokenStr; pair.cookieStr;
await cookies.set("csrf_cookie", pair.cookieStr); response.body = ` <html> <body> <form METHOD="POST"> <input type="text" name="text"> <input type="hidden" name="csrf_token" value="${pair.tokenStr}"> <button type="submit">submit</button> </form> </body> </html> `; });
router.get("/", ({ response }: Context) => { response.body = "success"; });
const app = new Application(); app.use(router.routes()); app.use(router.allowedMethods());
export { app };
|
app.ts1 2 3
| import { app } from "./server.ts";
app.listen({ port: 8080 });
|
テストコード
アプリケーションの内容に基づいて、次のシナリオでテストを用意します。
- Form を取得するための GET リクエストを実行
- レスポンスの Header から、
set-cookie
を文字列操作し、cookie の値を取得
- アプリケーションが返してくる HTML をパースし、トークンの取得
- Cookie とトークンを含めて POST リクエスト
- レスポンスの内容が
/
へのリダイレクトであることを確認
テストコードは、次のようになります。
app_test.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import { superoak } from "https://deno.land/x/superoak@4.7.0/mod.ts"; import * as queryString from "https://deno.land/x/querystring@v1.0.2/mod.js"; import { assertEquals } from "https://deno.land/std@0.148.0/testing/asserts.ts"; import { Document, DOMParser, } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts"; import { app } from "./server.ts";
Deno.test("#1 form, cookie test", async () => { const request1 = await superoak(app); const getForm = await request1.get("/form");
const csrfCookieValue = `${getForm.headers["set-cookie"]}` .split(";")[0] .split("csrf_cookie=")[1];
const doc1: Document = new DOMParser().parseFromString( getForm.text, "text/html" )!; const csrfToken = doc1.querySelector('[name="csrf_token"]'); const csrfTokenValue = csrfToken?.getAttribute("value") || "";
const request2 = await superoak(app); const response = await request2 .post("/form") .set("content-type", "application/x-www-form-urlencoded") .set("cookie", `csrf_cookie=${csrfCookieValue}; `) .send(`csrf_token=${encodeURIComponent(csrfTokenValue)}&text=test-text`) .expect(302);
assertEquals(response.headers["location"], "/"); });
|
実行
以下の通り実行します。
1 2 3 4 5 6 7 8 9
| $ CRYPT_KEY=01234567012345670123456701234568 deno test -A app_test.ts Check file:///usr/src/app/app_test.ts
Failed token from HMAC verification not pair. ... ok (4ms)
ok | 9 passed | 0 failed (236ms)
|
テストが通りました。
というわけで、作りたかったテストを書けました。
cookie 周りの操作は、「なるほど、これでいいのか感」がありました。
ブラウザで実際に動作させた場合と同じ操作を再現、アプリ側に不要な考慮をせずに済んだのが良いところです。
今後 oak で込み入ったもの作るときには、同じ方法でテストを作り込んでいきたいところです。
ではでは。