2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ScalaAdvent Calendar 2019

Day 2

scalaの認証ライブラリSilhouetteでの認証をかましたリクエストテスト方法

Posted at

はじめに

scalaの認証テストsilhoetteを使った場合のリクエストテストの方法。
routeに対して認証をかけた時、普通にリクエストテストを実行すると落ちるが、それを回避するためのtips。
内容は殆ど公式準拠。

Test Helpers

playを拡張するヘルパーライブラリをまずinstall。

libraryDependencies ++= Seq(
	"com.mohiva" %% "play-silhouette-testkit" % "version" % "test"
)

テストのファイルにimport。

import com.mohiva.play.silhouette.test._

併用するテストフレームワークは選ばないが、以下の例はすべてSpecs2。

Test silhouette actions

以下のようなコントローラをテストしたい。
その場合、IdentityとAuthenticatorをハンドルするFaceEnvironmentを使用してテストを実行する。

class UserController(silhouette: Silhouette[MyEnv]) extends Controller {

  /**
   * userを取得.
   * 認証あり.
   */
  def user = silhouette.SecuredAction { implicit request =>
    // 認証が通っている場合はok
    Ok(Json.toJson(request.identity))
  }

  /**
   * userが認証されているかチェック.
   * コントローラ自体に認証はかかっていないがどのユーザからのアクセスかは知覚する.
   */
  def isAuthenticated = silhouette.UserAwareAction { implicit request =>
    request.identity match {
      case Some(identity) => Ok  // 認証済みuserからのアクセスはok
      case None => Unauthorized  // 認証されていないuserからのアクセスはUnauthorized
    }
  }
}

FakeEnvironment

テスト用のFakeEnvironmentにはLoginInfoとIdentityのペアを渡しさえすれば良い。
あとは勝手にFakeIdentityServicerやAuthenticatorServiceをいい感じに作成してくれる。

val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))

FakeRequest

silhouetteのテストヘルパーはplay組み込みテストヘルパーのFakeRequestを拡張する。

1つ目のメソッドはAuthenticatorを受け取りリクエスト内に組み込む。

val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val authenticator = new CookieAuthenticator("test", identity.loginInfo, ...)
val request = FakeRequest().withAuthenticator(authenticator)

2つ目のメソッドはAuthenticatorの元になるLoginInfoを受け取り、リクエストに組み込む。

val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest().withAuthenticator(identity.loginInfo)

NOTE
これらのメソッドを使用する際にはimplicitなenvironmentを同じスコープにおいておく必要がある。

実際のテスト

authenticator(認証子)の欠落ケースのテスト

リクエスト内にtokenなどの認証子がないケースのテストでは、リクエストに何も渡さずにテストすればいい。

class UserSpec extends PlaySpecification {

  "The `user` method" should {
    "authenticatorがない場合401が返る" in new WithApplication {
      val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com"))
      val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
      val request = FakeRequest() // リクエストに何も渡さない

      val controller = app.injector.instanceOf[UserController]
      val result = controller.user(request)

      status(result) must equalTo(UNAUTHORIZED)
    }
  }

  "The `isAuthenticated` method" should {
    "authenticatorがない場合401が返る" in new WithApplication {
      val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com"))
      val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
      val request = FakeRequest() // リクエストに何も渡さない

      val controller = app.injector.instanceOf[UserController]
      val result = controller.isAuthenticated(request)

      status(result) must equalTo(UNAUTHORIZED)
    }
  }
}

Identity(認証対象)が存在しないケースのテスト

authenticatorに対応するidentityがないケースをテストする場合、user(ここで言うIdentity)とauthenticatorに異なるlogin infoを渡せばいい。

class UserSpec extends PlaySpecification {

  "The `user` method" should {
    "return status 401 if authenticator but no identity was found" in new WithApplication {
      val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com")) // ここのlogin infoと、
      implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
      val request = FakeRequest()
        .withAuthenticator(LoginInfo("xing", "comedian@watchmen.com")) // ここのlogin infoが違う

      val controller = app.injector.instanceOf[UserController]
      val result = controller.user(request)

      status(result) must equalTo(UNAUTHORIZED)
    }
  }

  "The `isAuthenticated` method" should {
    "return status 401 if authenticator but no identity was found" in new WithApplication {
      val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com")) // ここのlogin infoと、
      implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
      val request = FakeRequest()
        .withAuthenticator(LoginInfo("xing", "comedian@watchmen.com")) // ここのlogin infoが異なる

      val controller = app.injector.instanceOf[UserController]
      val result = controller.isAuthenticated(request)

      status(result) must equalTo(UNAUTHORIZED)
    }
  }
}

Identityの正常認証ケースのテスト

正常な認証ケースをテストしたい場合同じlogin infoをidentityとauthenticatorに渡せばいい。

class UserSpec extends PlaySpecification {

  "The `user` method" should {
    "return status 200 if authenticator and identity was found" in new WithApplication {
      val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com")) // ここのlogin infoと、
      implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
      val request = FakeRequest().withAuthenticator(identity.loginInfo) // ここのlogin infoが同じ

      val controller = app.injector.instanceOf[UserController]
      val result = controller.user(request)

      status(result) must equalTo(OK)
    }
  }

  "The `isAuthenticated` method" should {
    "return status 200 if authenticator and identity was found" in new WithApplication {
      val identity = User(LoginInfo("facebook", "apollonia.vanova@watchmen.com")) // ここのlogin infoと、
      implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
      val request = FakeRequest().withAuthenticator(identity.loginInfo) // ここのlogin infoが同じ

      val controller = app.injector.instanceOf[UserController]
      val result = controller.isAuthenticated(request)

      status(result) must equalTo(OK)
    }
  }
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?