先日、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 で込み入ったもの作るときには、同じ方法でテストを作り込んでいきたいところです。
ではでは。