0
1

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 3 years have passed since last update.

アーキテクチャテストAdvent Calendar 2020

Day 14

ArchUnit 実践:実装上、重要なメソッドアノテーションの付与忘れをチェックする

Last updated at Posted at 2020-12-14
// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* Spring Boot 2.4.0
* JUnit 5.7.0
* ArchUnit 0.14.1

アーキテクチャテストのモチベーション

アプリケーション固有の重要な実装ルールを担保したい。

  • バックエンドの REST API サービスを Spring Boot で構築している
  • API エンドポイントとなるハンドラーメソッドでは HandlerInterceptor と自作アノテーション(@Auth)によって、利用者の「認証」と「認可(権限チェック)」を行っている
  • 認証・認可のアノテーションを付与し忘れると、エンドポイントがパブリックに公開されてしまうため、付与忘れがないことを漏れなくチェックしたい

ハンドラーメソッドの実装イメージ

// ブログの新規投稿エンドポイント
// `EDITOR`権限以上のユーザのみエンドポイントを利用できる
@Auth(Role.EDITOR)
@PostMapping("/api/blogs")
public Map<String, Object> postBlog(@RequestBody final BlogCreateForm form) {
    // ...
}

アーキテクチャテストの実装

package com.example;
 
import com.example.presentation.Auth;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.*;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

class ArchitectureTest {

    // 検査対象のクラス
    private static final JavaClasses CLASSES =
            new ClassFileImporter()
                    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                    .importPackages("com.example");

    @Test
    void APIエンドポイントでは必ず認証と認可を行う() {
        methods()
            .that(new DescribedPredicate<>("are request handler") {
                /**
                 * @param method 検査対象クラスのメソッド
                 * @return ハンドラーメソッドである場合、true
                 */
                @Override
                public boolean apply(JavaMethod method) {
                    return method.isAnnotatedWith(RequestMapping.class)
                        || method.isAnnotatedWith(GetMapping.class)
                        || method.isAnnotatedWith(PostMapping.class)
                        || method.isAnnotatedWith(PutMapping.class)
                        || method.isAnnotatedWith(PatchMapping.class)
                        || method.isAnnotatedWith(DeleteMapping.class);
                }
            })
            .should(new ArchCondition<>("be annotated with @Auth") {
                @Override
                public void check(JavaMethod method, ConditionEvents events) {
                    if (! method.isAnnotatedWith(Auth.class)) {
                        // 実装ルール違反を通知
                        events.add(
                            SimpleConditionEvent.violated(method, String.format(
                                "`%s` is not annotated with @Auth.", method.getFullName()))
                        );
                    }
                }
            })
            .check(CLASSES);
    }
}
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?