@Test
public void testUpdateChecklist() throws Exception {

    final String pathPrefix = "/test";
    final File tempDir = new File(testBaseDir, "testUpdateChecklist");
    final File pass1Dir = new File(tempDir, "pass1/segmentstore");
    final File pass1Checklist = new File(tempDir, "pass1.chk.json");
    final File fullRepoDir = new File(tempDir, "fullRepo/segmentstore");
    final File diffRepoDir = new File(tempDir, "diffRepo/segmentstore");
    final File fullPassChecklist = new File(tempDir, "fullPass.chk.json");
    final File mergePassChecklist = new File(tempDir, "mergePass.chk.json");
    final File replacePassChecklist = new File(tempDir, "replacePass.chk.json");
    final File truncatePassChecklist = new File(tempDir, "truncatePass.chk.json");
    final int sample = 5;
    List<String> orderedPaths = IntStream.range(0, sample).mapToObj(val -> pathPrefix + "/ordered" + val).collect(Collectors.toList());
    List<String> unorderedPaths = IntStream.range(0, sample).mapToObj(val -> pathPrefix + "/unordered" + val).collect(Collectors.toList());
    assertEquals("should generate n paths: ", sample, orderedPaths.size());
    final List<String> allPaths = new ArrayList<>(orderedPaths);
    allPaths.addAll(unorderedPaths);
    allPaths.add(pathPrefix);
    TestUtil.prepareRepo(pass1Dir, session -> {
        TestUtil.installCndFromURL(session, getClass().getResource("/sling_nodetypes.cnd"));
        orderedPaths.forEach(Fun.tryOrVoid1(path ->
                JcrUtils.getOrCreateByPath(path, "nt:folder",
                        "sling:OrderedFolder", session, true)));
        unorderedPaths.forEach(Fun.tryOrVoid1(path ->
                JcrUtils.getOrCreateByPath(path, "nt:folder",
                        "sling:Folder", session, true)));
    });
    TestUtil.prepareRepo(fullRepoDir, session -> {
        TestUtil.installCndFromURL(session, getClass().getResource("/sling_nodetypes.cnd"));
        allPaths.forEach(Fun.uncheckVoid1(path -> {
            Node node = JcrUtils.getOrCreateByPath(path, "sling:Folder", session);
            node.addMixin("mix:title");
            node.addMixin("sling:Resource");
        }));
        session.save();
    });
    TestUtil.prepareRepo(diffRepoDir, session -> {
        TestUtil.installCndFromURL(session, getClass().getResource("/sling_nodetypes.cnd"));
        allPaths.stream().filter(path -> path.matches(".*[02468]$"))
                .forEachOrdered(Fun.uncheckVoid1(path -> {
                    Node node = JcrUtils.getOrCreateByPath(path, "sling:Folder", session);
                    node.addMixin("mix:title");
                    node.addMixin("sling:Resource");
                }));
        session.save();
    });
    TestUtil.withReadOnlyFixture(pass1Dir, session -> {
        ChecklistExporter pathExporter = new ChecklistExporter.Builder().byPath(allPaths.toArray(new String[0])).build();
        pathExporter.updateChecklist(() -> new OutputStreamWriter(
                        new FileOutputStream(pass1Checklist), StandardCharsets.UTF_8),
                session, Checklist.fromJson("", null, obj()
                        .key(Checklist.keys().jcrNamespaces(), Collections
                                .singletonList(JcrNs.create("sling",
                                        "http://sling.apache.org/jcr/sling/1.0")))
                        .key(Checklist.keys().jcrPrivileges(), obj()
                                .key("sling:doesAll", obj()
                                        .key("contains", arr("sling:doOne", "sling:doTwo")))
                                .get())
                        .get()), null);
        try (JsonReader reader = Json.createReader(new FileInputStream(pass1Checklist))) {
            JsonObject checklist = reader.readObject();
            assertTrue("checklist object should contain the forcedRoots key",
                    checklist.containsKey(Checklist.keys().forcedRoots()));
            assertTrue("checklist object should contain the privileges key",
                    checklist.containsKey(Checklist.keys().jcrPrivileges()));
            JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots());
            assertEquals("forcedRoots should be array with expected number of elements", allPaths.size(),
                    forcedRoots.size());
            List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson);
            for (String path : unorderedPaths) {
                ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null);
                assertNotNull(String.format("[pass1] root for path %s should not be null", path), root0);
                assertEquals(String.format("[pass1] root primaryType for path %s should be sling:Folder", path),
                        "sling:Folder", root0.getPrimaryType());
            }
            for (String path : orderedPaths) {
                ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null);
                assertNotNull(String.format("[pass1] root for path %s should not be null", path), root0);
                assertEquals(String.format("[pass1] root primaryType for path %s should be sling:OrderedFolder", path),
                        "sling:OrderedFolder", root0.getPrimaryType());
            }
        }
    });
    TestUtil.withReadOnlyFixture(fullRepoDir, session -> {
        ChecklistExporter pathExporter = new ChecklistExporter.Builder().byPath(allPaths.toArray(new String[0])).build();
        try (JsonReader reader = Json.createReader(new FileInputStream(pass1Checklist))) {
            pathExporter.updateChecklist(() -> new OutputStreamWriter(
                            new FileOutputStream(fullPassChecklist), StandardCharsets.UTF_8),
                    session, Checklist.fromJson("", null, reader.readObject()), null);
        }
        try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) {
            JsonObject checklist = reader.readObject();
            assertTrue("checklist object should contain the forcedRoots key",
                    checklist.containsKey(Checklist.keys().forcedRoots()));
            assertTrue("checklist object should contain the privileges key",
                    checklist.containsKey(Checklist.keys().jcrPrivileges()));
            JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots());
            assertEquals("forcedRoots should be array with expected number of elements", allPaths.size(),
                    forcedRoots.size());
            List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson);
            for (String path : allPaths) {
                ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null);
                assertNotNull(String.format("[pass2] root for path %s should not be null", path), root0);
                assertEquals(String.format("[pass2] root primaryType for path %s should be sling:Folder", path),
                        "sling:Folder", root0.getPrimaryType());
                assertTrue(String.format("[pass2] root mixinTypes for path %s should contain mix:title", path),
                        root0.getMixinTypes().contains("mix:title"));
            }
        }
    });
    TestUtil.withReadOnlyFixture(diffRepoDir, session -> {
        ChecklistExporter diffExporter = new ChecklistExporter.Builder()
                .byNodeType("sling:Folder")
                .withScopePaths(Rules.fromJsonArray(arr(key("type", "include").key("pattern", pathPrefix + "/ordered.*")).get()))
                .withNodeTypeFilters(Rules.fromJsonArray(arr(key("type", "exclude").key("pattern", "sling:.*")).get()))
                .build();
        try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) {
            diffExporter.updateChecklist(() -> new OutputStreamWriter(
                            new FileOutputStream(mergePassChecklist), StandardCharsets.UTF_8),
                    session, Checklist.fromJson("", null, reader.readObject()),
                    ChecklistExporter.ForcedRootUpdatePolicy.MERGE);
        }
        try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) {
            diffExporter.updateChecklist(() -> new OutputStreamWriter(
                            new FileOutputStream(replacePassChecklist), StandardCharsets.UTF_8),
                    session, Checklist.fromJson("", null, reader.readObject()),
                    ChecklistExporter.ForcedRootUpdatePolicy.REPLACE);
        }
        try (JsonReader reader = Json.createReader(new FileInputStream(fullPassChecklist))) {
            diffExporter.updateChecklist(() -> new OutputStreamWriter(
                            new FileOutputStream(truncatePassChecklist), StandardCharsets.UTF_8),
                    session, Checklist.fromJson("", null, reader.readObject()),
                    ChecklistExporter.ForcedRootUpdatePolicy.TRUNCATE);
        }
        try (JsonReader reader = Json.createReader(new FileInputStream(mergePassChecklist))) {
            JsonObject checklist = reader.readObject();
            assertTrue("checklist object should contain the forcedRoots key",
                    checklist.containsKey(Checklist.keys().forcedRoots()));
            JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots());
            assertEquals("[mergePass] forcedRoots should be array with expected number of elements", allPaths.size(),
                    forcedRoots.size());
            List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson);
            for (String path : allPaths) {
                ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null);
                if (path.matches(".*/ordered[02468]$")) {
                    assertNotNull(String.format("[mergePass] root for path %s should not be null", path), root0);
                    assertNull(String.format("[mergePass] root primaryType for path %s should be sling:Folder", path),
                            root0.getPrimaryType());
                    assertTrue(String.format("[mergePass] root mixinTypes for path %s should contain mix:title", path),
                            root0.getMixinTypes().contains("mix:title"));
                    assertFalse(String.format("[mergePass] root mixinTypes for path %s should NOT contain sling:Resource", path),
                            root0.getMixinTypes().contains("sling:Resource"));
                } else {
                    assertNotNull(String.format("[mergePass] root for path %s should not be null", path), root0);
                    assertEquals(String.format("[mergePass] root primaryType for path %s should be sling:Folder", path),
                            "sling:Folder", root0.getPrimaryType());
                    assertTrue(String.format("[mergePass] root mixinTypes for path %s should contain mix:title", path),
                            root0.getMixinTypes().contains("mix:title"));
                    assertTrue(String.format("[mergePass] root mixinTypes for path %s should contain sling:Resource", path),
                            root0.getMixinTypes().contains("sling:Resource"));
                }
            }
        }
        try (JsonReader reader = Json.createReader(new FileInputStream(replacePassChecklist))) {
            JsonObject checklist = reader.readObject();
            assertTrue("checklist object should contain the forcedRoots key",
                    checklist.containsKey(Checklist.keys().forcedRoots()));
            JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots());
            assertEquals("[replacePass] forcedRoots should be array with expected number of elements",
                    1 + unorderedPaths.size() + orderedPaths.stream().filter(path -> path.matches(".*[02468]$")).count(),
                    forcedRoots.size());
            List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson);
            for (String path : allPaths) {
                ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null);
                if (path.matches(".*/ordered[02468]$")) {
                    assertNotNull(String.format("[replacePass] root for path %s should not be null", path), root0);
                    assertNull(String.format("[replacePass] root primaryType for path %s should be sling:Folder", path),
                            root0.getPrimaryType());
                    assertTrue(String.format("[replacePass] root mixinTypes for path %s should contain mix:title", path),
                            root0.getMixinTypes().contains("mix:title"));
                    assertFalse(String.format("[replacePass] root mixinTypes for path %s should NOT contain sling:Resource", path),
                            root0.getMixinTypes().contains("sling:Resource"));
                } else if (path.equals(pathPrefix) || path.matches(".*/unordered.*")) {
                    assertNotNull(String.format("[replacePass] root for path %s should not be null", path), root0);
                    assertEquals(String.format("[replacePass] root primaryType for path %s should be sling:Folder", path),
                            "sling:Folder", root0.getPrimaryType());
                    assertTrue(String.format("[replacePass] root mixinTypes for path %s should contain mix:title", path),
                            root0.getMixinTypes().contains("mix:title"));
                    assertTrue(String.format("[replacePass] root mixinTypes for path %s should contain sling:Resource", path),
                            root0.getMixinTypes().contains("sling:Resource"));
                } else {
                    assertNull(String.format("[replacePass] root for path %s should be null", path), root0);
                }
            }
        }
        try (JsonReader reader = Json.createReader(new FileInputStream(truncatePassChecklist))) {
            JsonObject checklist = reader.readObject();
            assertTrue("checklist object should contain the forcedRoots key",
                    checklist.containsKey(Checklist.keys().forcedRoots()));
            JsonArray forcedRoots = checklist.getJsonArray(Checklist.keys().forcedRoots());
            assertEquals("[truncatePass] forcedRoots should be array with expected number of elements",
                    orderedPaths.stream().filter(path -> path.matches(".*[02468]$")).count(), forcedRoots.size());
            List<ForcedRoot> readRoots = JavaxJson.mapArrayOfObjects(forcedRoots, ForcedRoot::fromJson);
            for (String path : allPaths) {
                ForcedRoot root0 = readRoots.stream().filter(root -> path.equals(root.getPath())).findFirst().orElse(null);
                if (path.matches(".*/ordered[02468]$")) {
                    assertNotNull(String.format("[truncatePass] root for path %s should not be null", path), root0);
                    assertNull(String.format("[truncatePass] root primaryType for path %s should be sling:Folder", path),
                            root0.getPrimaryType());
                    assertTrue(String.format("[truncatePass] root mixinTypes for path %s should contain mix:title", path),
                            root0.getMixinTypes().contains("mix:title"));
                    assertFalse(String.format("[truncatePass] root mixinTypes for path %s should NOT contain sling:Resource", path),
                            root0.getMixinTypes().contains("sling:Resource"));
                } else {
                    assertNull(String.format("[truncatePass] root for path %s should not be null", path), root0);
                }
            }
        }
    });
}