@Test
public void testSuppressAfterExtractViolationIfExpectationSatisfiedAfterScanPackage_multipleViolators() throws Exception {

    final String foo1 = "/foo1";
    final String foo2 = "/foo2";
    final PackageId container = PackageId.fromString("container");
    final PackageId embed1 = PackageId.fromString("embed1");
    SlingInstallable installable1 = mock(SlingInstallable.class);
    when(installable1.getParentId()).thenReturn(embed1);
    EmbeddedPackageInstallable epInstallable1 = new EmbeddedPackageInstallable(container, "", embed1);
    final PackageId embed2 = PackageId.fromString("embed2");
    SlingInstallable installable2 = mock(SlingInstallable.class);
    when(installable2.getParentId()).thenReturn(embed2);
    EmbeddedPackageInstallable epInstallable2 = new EmbeddedPackageInstallable(container, "", embed2);
    final PackageId embed3 = PackageId.fromString("embed3");
    SlingInstallable installable3 = mock(SlingInstallable.class);
    when(installable3.getParentId()).thenReturn(embed3);
    EmbeddedPackageInstallable epInstallable3 = new EmbeddedPackageInstallable(container, "", embed3);
    final Principal principal = new PrincipalImpl("nouser");
    final List<String> repoInits = Arrays.asList(
            "create service user nouser",
            "create path (nt:folder) /foo1",
            "set ACL on /foo1",
            "  allow jcr:read for nouser",
            "end",
            "create path (nt:folder) /foo2",
            "set ACL on /foo2",
            "  allow jcr:read for nouser",
            "end"
    );
    final ExpectAces.Check check1 = checkFor(obj()
            .key(ExpectAces.keys().principal(), "nouser")
            .key(ExpectAces.keys().expectedAces(), arr()
                    .val("type=allow;path=/foo1;privileges=jcr:read")
                    .val("type=allow;path=/foo1;privileges=rep:write")
            )
            .get());
    new OakpalPlan.Builder(null, null)
            .withRepoInits(repoInits)
            .build().toOakMachineBuilder(null, getClass().getClassLoader()).build()
            .adminInitAndInspect(session -> {
                check1.startedScan();
                check1.identifyPackage(container, null);
                check1.afterExtract(container, session);
                check1.beforeSlingInstall(container, epInstallable1, session);
                check1.identifyEmbeddedPackage(embed1, container, epInstallable1);
                check1.afterExtract(embed1, session);
                check1.beforeSlingInstall(container, epInstallable2, session);
                check1.identifyEmbeddedPackage(embed2, container, epInstallable2);
                check1.afterExtract(embed2, session);
                check1.beforeSlingInstall(container, epInstallable3, session);
                check1.identifyEmbeddedPackage(embed3, container, epInstallable3);
                check1.afterExtract(embed3, session);
                check1.beforeSlingInstall(container, installable1, session);
                check1.beforeSlingInstall(container, installable2, session);
                check1.beforeSlingInstall(container, installable3, session);
                check1.afterScanPackage(container, session);
                check1.finishedScan();
            });
    Assert.assertEquals("expected violation count", 1,
            check1.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("expected: ")).count());
    Assert.assertEquals("expected violated spec ends with rep:write", 1,
            check1.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("expected: ")
                            && viol.getDescription().contains("/foo1")
                            && viol.getDescription().endsWith("rep:write")).count());
    Assert.assertEquals("no unexpected violations", 0,
            check1.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("unexpected: ")).count());
    final Violation exViolation1 = check1.getReportedViolations().stream().findFirst().get();
    assertEquals("expected violators",
            Stream.of(container).collect(Collectors.toSet()),
            new HashSet<>(exViolation1.getPackages()));
    new OakpalPlan.Builder(null, null)
            .withRepoInits(repoInits)
            .build().toOakMachineBuilder(null, getClass().getClassLoader()).build()
            .adminInitAndInspect(session -> {
                check1.startedScan();
                check1.identifyPackage(container, null);
                check1.afterExtract(container, session);
                check1.beforeSlingInstall(container, epInstallable1, session);
                check1.identifyEmbeddedPackage(embed1, container, epInstallable1);
                resetAcl(session.getNode(foo1), (acl, privMan) -> {
                    Stream.of(acl.getAccessControlEntries())
                            .filter(composeTest1(AccessControlEntry::getPrincipal, principal::equals))
                            .forEachOrdered(uncheckVoid1(acl::removeAccessControlEntry));
                    acl.addEntry(principal, new Privilege[]{privMan.getPrivilege("rep:write")}, true);
                });
                check1.afterExtract(embed1, session);
                check1.beforeSlingInstall(container, epInstallable2, session);
                check1.identifyEmbeddedPackage(embed2, container, epInstallable2);
                check1.afterExtract(embed2, session);
                check1.beforeSlingInstall(container, epInstallable3, session);
                check1.identifyEmbeddedPackage(embed3, container, epInstallable3);
                check1.afterExtract(embed3, session);
                check1.beforeSlingInstall(container, installable1, session);
                check1.beforeSlingInstall(container, installable2, session);
                check1.beforeSlingInstall(container, installable3, session);
                check1.afterScanPackage(container, session);
                check1.finishedScan();
            });
    Assert.assertEquals("expected violation count", 1,
            check1.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("expected: ")).count());
    Assert.assertEquals("expected violated spec ends with jcr:read", 1,
            check1.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("expected: ")
                            && viol.getDescription().contains("/foo1")
                            && viol.getDescription().endsWith("jcr:read")).count());
    Assert.assertEquals("no unexpected violations", 0,
            check1.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("unexpected: ")).count());
    final Violation exViolation2 = check1.getReportedViolations().stream().findFirst().get();
    assertEquals("expected violators",
            Stream.of(embed1, container).collect(Collectors.toSet()),
            new HashSet<>(exViolation2.getPackages()));
    final ExpectAces.Check check2 = checkFor(obj()
            .key(ExpectAces.keys().principal(), "nouser")
            .key(ExpectAces.keys().notExpectedAces(), arr()
                    .val("type=allow;path=/foo2;privileges=jcr:read")
                    .val("type=allow;path=/foo2;privileges=rep:write")
                    // foo3 is not created. a non-existent path should satisfy not-expected aces
                    .val("type=allow;path=/foo3;privileges=rep:write")
            )
            .get());
    new OakpalPlan.Builder(null, null)
            .withRepoInits(repoInits)
            .build().toOakMachineBuilder(null, getClass().getClassLoader()).build()
            .adminInitAndInspect(session -> {
                check2.startedScan();
                check2.identifyPackage(container, null);
                check2.afterExtract(container, session);
                check2.beforeSlingInstall(embed1, epInstallable1, session);
                check2.identifyEmbeddedPackage(embed1, container, epInstallable1);
                check2.afterExtract(embed1, session);
                check2.beforeSlingInstall(embed2, epInstallable2, session);
                check2.identifyEmbeddedPackage(embed2, container, epInstallable2);
                check2.afterExtract(embed2, session);
                check2.beforeSlingInstall(embed3, epInstallable3, session);
                check2.identifyEmbeddedPackage(embed3, container, epInstallable3);
                check2.afterExtract(embed3, session);
                check2.beforeSlingInstall(container, installable1, session);
                check2.beforeSlingInstall(container, installable2, session);
                check2.beforeSlingInstall(container, installable3, session);
                check2.afterScanPackage(container, session);
                check2.finishedScan();
            });
    Assert.assertEquals("unexpected violation count", 1,
            check2.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("unexpected: ")).count());
    Assert.assertEquals("unexpected violated spec ends with jcr:read", 1,
            check2.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("unexpected: ")
                            && viol.getDescription().contains("/foo2")
                            && viol.getDescription().endsWith("jcr:read")).count());
    Assert.assertEquals("no expected violations", 0,
            check2.getReportedViolations().stream()
                    .filter(viol -> viol.getDescription().startsWith("expected: ")).count());
    final Violation unViolation1 = check2.getReportedViolations().stream().findFirst().get();
    assertEquals("expected violators",
            Stream.of(container).collect(Collectors.toSet()),
            new HashSet<>(unViolation1.getPackages()));
    new OakpalPlan.Builder(null, null)
            .withRepoInits(repoInits)
            .build().toOakMachineBuilder(null, getClass().getClassLoader()).build()
            .adminInitAndInspect(session -> {
                check2.startedScan();
                check2.identifyPackage(container, null);
                resetAcl(session.getNode(foo2), (acl, priv) -> {
                    Stream.of(acl.getAccessControlEntries())
                            .forEachOrdered(uncheckVoid1(acl::removeAccessControlEntry));
                });
                check2.afterExtract(container, session);
                check2.beforeSlingInstall(embed1, epInstallable1, session);
                check2.identifyEmbeddedPackage(embed1, container, epInstallable1);
                resetAcl(session.getNode(foo2), (acl, priv) -> {
                    acl.addEntry(principal, Stream.of("rep:write")
                            .map(uncheck1(priv::getPrivilege)).toArray(Privilege[]::new), true);
                });
                check2.afterExtract(embed1, session);
                check2.beforeSlingInstall(embed2, epInstallable2, session);
                check2.identifyEmbeddedPackage(embed2, container, epInstallable2);
                check2.afterExtract(embed2, session);
                check2.beforeSlingInstall(embed3, epInstallable3, session);
                check2.identifyEmbeddedPackage(embed3, container, epInstallable3);
                session.getNode(foo2).remove();
                check2.afterExtract(embed3, session);
                check2.beforeSlingInstall(container, installable1, session);
                check2.beforeSlingInstall(container, installable2, session);
                Node node3 = JcrUtils.getOrCreateByPath("/foo3", "nt:folder", session);
                resetAcl(node3, (acl, priv) -> {
                    acl.addEntry(principal, new Privilege[]{priv.getPrivilege("rep:write")}, true);
                });
                check2.appliedRepoInitScripts(container, Collections.emptyList(), installable2, session);
                check2.beforeSlingInstall(container, installable3, session);
                check2.afterScanPackage(container, session);
                check2.finishedScan();
            });
    assertEquals("expect 2 unexpected violations", 2, check2.getReportedViolations().size());
    final Collection<Violation> unViolations = check2.getReportedViolations();
    assertTrue("all start with unexpected " + unViolations, unViolations.stream()
            .allMatch(vio -> vio.getDescription().startsWith("unexpected: ")));
    assertEquals("one contains /foo2: " + unViolations, 1, unViolations.stream()
            .filter(vio -> vio.getDescription().contains("/foo2")).count());
    assertEquals("one contains /foo3: " + unViolations, 1, unViolations.stream()
            .filter(vio -> vio.getDescription().contains("/foo3")).count());
    assertEquals("expected violators of /foo2",
            Stream.of(embed1).collect(Collectors.toSet()), unViolations.stream()
                    .filter(vio -> vio.getDescription().contains("/foo2"))
                    .flatMap(Fun.compose1(Violation::getPackages, Collection::stream))
                    .collect(Collectors.toSet()));
    assertEquals("expected violators of /foo3",
            Stream.of(embed2, container).collect(Collectors.toSet()), unViolations.stream()
                    .filter(vio -> vio.getDescription().contains("/foo3"))
                    .flatMap(Fun.compose1(Violation::getPackages, Collection::stream))
                    .collect(Collectors.toSet()));
}