ConstraintViolationSetAssert.java

package io.extact.rms.test.assertj;

import java.util.List;
import java.util.Set;

import jakarta.validation.ConstraintViolation;

import org.assertj.core.api.AbstractAssert;

@SuppressWarnings("rawtypes")
public class ConstraintViolationSetAssert extends AbstractAssert<ConstraintViolationSetAssert, Set<? extends ConstraintViolation>> {

    public ConstraintViolationSetAssert(Set<? extends ConstraintViolation> actual) {
        super(actual, ConstraintViolationSetAssert.class);
    }

    public static ConstraintViolationSetAssert assertThat(Set<? extends ConstraintViolation> actual) {
        return new ConstraintViolationSetAssert(actual);
    }

    public ConstraintViolationSetAssert hasSize(int size) {
        isNotNull();
        if (actual.size() != size) {
            List<String> messages = actual.stream()
                    .map(ConstraintViolation::getMessageTemplate)
                    .toList();
            failWithMessage("Expecting %s violations, but there are %s violations. Violation messages: <%s>", size, actual.size(), messages);
        }
        return this;
    }

    public ConstraintViolationSetAssert hasViolationOnPath(String path) {
        isNotNull();
        if (!containsViolationWithPath(actual, path)) {
            List<String> paths = actual.stream()
                    .map(violation -> violation.getPropertyPath().toString())
                    .toList();

            failWithMessage("There was no violation with path <%s>. Violation paths: <%s>", path, paths);
        }
        return this;
    }

    public ConstraintViolationSetAssert hasViolationOnPathConstaining(String path) {
        isNotNull();
        if (!containsViolationWithPathContaining(actual, path)) {
            List<String> paths = actual.stream()
                    .map(violation -> violation.getPropertyPath().toString())
                    .toList();

            failWithMessage("There was no violation with path constaining <%s>. Violation paths: <%s>", path, paths);
        }
        return this;
    }

    public ConstraintViolationSetAssert hasMessageEndingWith(String suffix) {
        isNotNull();
        if (!containsViolationWithMessageEndingWith(actual, suffix)) {
            List<String> messages = actual.stream()
                    .map(ConstraintViolation::getMessageTemplate)
                    .toList();
            failWithMessage("There was no violation with message ending with <%s> . Violation messages: <%s>", suffix, messages);
        }
        return this;
    }

    public ConstraintViolationSetAssert hasNoViolations() {
        isNotNull();
        if (!actual.isEmpty()) {
            List<String> messages = actual.stream()
                    .map(ConstraintViolation::getMessageTemplate)
                    .toList();
            failWithMessage("Expecting no violations, but there are %s violations. Violation messages: <%s>", actual.size(), messages);
        }
        return this;
    }

    private boolean containsViolationWithPath(Set<? extends ConstraintViolation> violations, String path) {
        return violations.stream()
                    .anyMatch(violation -> violation.getPropertyPath().toString().equals(path));
    }

    private boolean containsViolationWithPathContaining(Set<? extends ConstraintViolation> violations, String path) {
        return violations.stream()
                    .anyMatch(violation -> violation.getPropertyPath().toString().contains(path));
    }

    private boolean containsViolationWithMessageEndingWith(Set<? extends ConstraintViolation> violations, String suffix) {
        return violations.stream()
                .map(violation -> violation.getMessageTemplate().replace("{", "").replace("}", ""))
                .anyMatch(message -> message.endsWith(suffix));
    }
}