WordPressのカスタム投稿タイプの権限をPHPUnitでテスト


WordPressのカスタム投稿タイプを作ってユーザーの種類ごとに権限をいろいろ割り当てたとしますよね。

その時にそれがちゃんと意図した通りに機能しているかをチェックしたいわけですが、ユーザーを作って何回もログインしたりログアウトしたりっていうのは、めんどくさい上に修正を繰り返すうちに怪しいことになってくることもあるわけで、この確認作業はなんとしても自動化すべきです。

## カスタム投稿タイプをつくる

今回は以下のような感じでカスタム投稿タイプをつくりました。

```php
register_post_type( 'log', array(
	'labels'            => array(
		'name'                => "Logs",
	),
	'public'            => false,
	'show_ui'           => true,
	'menu_icon'         => 'dashicons-list-view',
	'show_in_rest'      => false,
	'rest_base'         => 'log',
	'rest_controller_class' => 'WP_REST_Posts_Controller',
	'capability_type'   => 'page',
	'capabilities'      => array(
		'create_posts' => 'do_not_allow',
		'delete_posts' => 'do_not_allow',
	),
	'map_meta_cap'      => false,
) );
```

上の例で今回重要なのは以下の部分です。

```php
	'capability_type'   => 'page',
	'capabilities'      => array(
		'create_posts' => 'do_not_allow',
		'delete_posts' => 'do_not_allow',
	),
	'map_meta_cap'      => false,
```

今回やりたいのは、

* `log` というカスタム投稿タイプを作る。
* ベースの権限は、固定ページと同じにする。
* ただし、記事の新規追加と削除、編集はさせたくない。

このユースケースでは実際の記事は自動的に放り込まれることを想定していて、それを管理画面の表で見れるだけでいいっていう感じです。

管理画面の表というのは以下のようなよくみるやつです。

![](https://www.evernote.com/l/ABWRA1TZ24tOjrN9IHkmoYF7GMpgFN6t8U8B/image.png)

要はこれ以外は誰にも見せたくないし、この表を見れるのも `administrator` と `editor` だけに限定したいということです。

この例では `administrator` でも編集画面に行くと以下のようになります。

![](https://www.evernote.com/l/ABWorHd2B-pCtY5P4wV4aN-H_ko1ObiEFG0B/image.png)

`map_meta_cap` の値を `true` にすると編集画面が表示されます。

## PHPUnit で確認

これを実際にユニットテストで確認するには以下のような感じです。

```php
<?php

class Posttype_Test extends WP_UnitTestCase
{
	public function test_log_post_type_should_exist()
	{
		$this->assertTrue( in_array( 'log', get_post_types() ) );
	}

	public function test_log_capabilities()
	{
		$post_type_object = get_post_type_object( 'log' );

		$this->set_current_user( 'administrator' );
		$this->assertTrue( current_user_can( $post_type_object->cap->edit_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->create_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->delete_posts ) );

		$this->set_current_user( 'editor' );
		$this->assertTrue( current_user_can( $post_type_object->cap->edit_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->create_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->delete_posts ) );

		$this->set_current_user( 'author' );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->edit_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->create_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->delete_posts ) );

		$this->set_current_user( 'contributor' );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->edit_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->create_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->delete_posts ) );

		$this->set_current_user( 'subscriber' );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->edit_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->create_posts ) );
		$this->assertTrue( ! current_user_can( $post_type_object->cap->delete_posts ) );
	}
	
	/**
	 * Add user and set the user as current user.
	 *
	 * @param  string $role administrator, editor, author, contributor ...
	 * @return none
	 */
	private function set_current_user( $role )
	{
		$user = $this->factory()->user->create_and_get( array(
			'role' => $role,
		) );

		/*
		 * Set $user as the current user
		 */
		wp_set_current_user( $user->ID, $user->user_login );
	}
}
```

まず1つ目の関数で、目的のカスタム投稿タイプが存在しているかどうかを確認します。

WP-CLI で生成したユニットテスト用環境であれば、そのプラグインが `bootstrap.php` で有効化されていますので、このファイルを `tests` ディレクトリ直下において `phpunit` と叩くだけです。

2つ目の関数では、WordPressの各ロールごとに、意図した通りに権限が割り当てられているかどうかを確認しています。

`!` がついてるやつは、その権限がないはずということを確認しています。

だらだらと全部をわざわざ書いてるのは、毎回いちいち調べるのがめんどうなので、これをコピペして `!` をつけたり消したりするからです。

2つ目の関数で使っている `set_current_user()` というメソッドは、PHPUnitの中でランダムなユーザーを作ってそれをログインユーザーとしてテストしてねという関数で、3つ目に定義されている関数がそれです。