@Test
public void testV5() {
    GUID prev = GUID.v5(GUID.NIL, "");
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        GUID namespace = new GUID(UUID.randomUUID());
        String name = UUID.randomUUID().toString();
        GUID guid = GUID.v5(namespace, name);
        assertNotNull(guid);
        assertNotEquals(prev, guid);
        assertEquals(5, guid.version());
        assertNotEquals(GUID.NIL, guid);
        assertNotEquals(GUID.MAX, guid);
        assertNotEquals(GUID.v5(null, name), GUID.v5(namespace, name));
        assertNotEquals(GUID.v5(GUID.NIL, name), GUID.v5(namespace, name));
        assertNotEquals(GUID.v5(namespace, name), GUID.v3(namespace, name));
        assertEquals(GUID.v5(null, name), GUID.v5(null, name));
        assertEquals(GUID.v5(GUID.NIL, name), GUID.v5(GUID.NIL, name));
        assertEquals(GUID.v5(namespace, name), GUID.v5(namespace, name));
        assertEquals(GUID.v5(namespace, name), GUID.v5(namespace, name.getBytes(StandardCharsets.UTF_8)));
        prev = guid;
    }
    {
        GUID guid = GUID.v5(GUID.NAMESPACE_DNS, "www.example.com");
        assertEquals("2ed6657d-e927-568b-95e1-2665a8aea6a2", guid.toString());
    }
}

@Test
public void testV5() {
    GUID prev = GUID.v5(GUID.NIL, "");
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        GUID namespace = new GUID(UUID.randomUUID());
        String name = UUID.randomUUID().toString();
        GUID guid = GUID.v5(namespace, name);
        assertNotNull(guid);
        assertNotEquals(prev, guid);
        assertEquals(5, guid.version());
        assertNotEquals(GUID.NIL, guid);
        assertNotEquals(GUID.MAX, guid);
        assertNotEquals(GUID.v5(null, name), GUID.v5(namespace, name));
        assertNotEquals(GUID.v5(GUID.NIL, name), GUID.v5(namespace, name));
        assertNotEquals(GUID.v5(namespace, name), GUID.v3(namespace, name));
        assertEquals(GUID.v5(null, name), GUID.v5(null, name));
        assertEquals(GUID.v5(GUID.NIL, name), GUID.v5(GUID.NIL, name));
        assertEquals(GUID.v5(namespace, name), GUID.v5(namespace, name));
        prev = guid;
    }
    {
        GUID guid = GUID.v5(GUID.NAMESPACE_DNS, "www.example.com");
        assertEquals("2ed6657d-e927-568b-95e1-2665a8aea6a2", guid.toString());
    }
}

public static GUID v5(GUID namespace, byte[] bytes) {
    return hash(5, "SHA-1", namespace, bytes);
}

@Test
public void testV3() {
    GUID prev = GUID.v3(GUID.NIL, "");
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        GUID namespace = new GUID(UUID.randomUUID());
        String name = UUID.randomUUID().toString();
        GUID guid = GUID.v3(namespace, name);
        assertNotNull(guid);
        assertNotEquals(prev, guid);
        assertEquals(3, guid.version());
        assertNotEquals(GUID.NIL, guid);
        assertNotEquals(GUID.MAX, guid);
        assertNotEquals(GUID.v3(null, name), GUID.v3(namespace, name));
        assertNotEquals(GUID.v3(GUID.NIL, name), GUID.v3(namespace, name));
        assertNotEquals(GUID.v3(namespace, name), GUID.v5(namespace, name));
        assertEquals(GUID.v3(null, name), GUID.v3(null, name));
        assertEquals(GUID.v3(GUID.NIL, name), GUID.v3(GUID.NIL, name));
        assertEquals(GUID.v3(namespace, name), GUID.v3(namespace, name));
        assertEquals(GUID.v3(namespace, name), GUID.v3(namespace, name.getBytes(StandardCharsets.UTF_8)));
        assertEquals(UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)), GUID.v3(null, name).toUUID());
        prev = guid;
    }
    {
        GUID guid = GUID.v3(GUID.NAMESPACE_DNS, "www.example.com");
        assertEquals("5df41881-3aed-3515-88a7-2f4a814cf09e", guid.toString());
    }
}

@Test
public void testV3() {
    GUID prev = GUID.v3(GUID.NIL, "");
    for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
        GUID namespace = new GUID(UUID.randomUUID());
        String name = UUID.randomUUID().toString();
        GUID guid = GUID.v3(namespace, name);
        assertNotNull(guid);
        assertNotEquals(prev, guid);
        assertEquals(3, guid.version());
        assertNotEquals(GUID.NIL, guid);
        assertNotEquals(GUID.MAX, guid);
        assertNotEquals(GUID.v3(null, name), GUID.v3(namespace, name));
        assertNotEquals(GUID.v3(GUID.NIL, name), GUID.v3(namespace, name));
        assertNotEquals(GUID.v3(namespace, name), GUID.v5(namespace, name));
        assertEquals(GUID.v3(null, name), GUID.v3(null, name));
        assertEquals(GUID.v3(GUID.NIL, name), GUID.v3(GUID.NIL, name));
        assertEquals(GUID.v3(namespace, name), GUID.v3(namespace, name));
        assertEquals(UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)), GUID.v3(null, name).toUUID());
        prev = guid;
    }
    {
        GUID guid = GUID.v3(GUID.NAMESPACE_DNS, "www.example.com");
        assertEquals("5df41881-3aed-3515-88a7-2f4a814cf09e", guid.toString());
    }
}

public static GUID v3(GUID namespace, byte[] bytes) {
    return hash(3, "MD5", namespace, bytes);
}


@Test
public void testLongRandomWithFactory() {
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = UuidUtil.setVersion(new UUID(list.get(0), list.get(1)), 4);
        RandomBasedFactory factory = new RandomBasedFactory(() -> list.remove(0));
        UUID uuid = factory.create();
        assertEquals(uuid1, uuid);
    }
    {
        SplittableRandom random = new SplittableRandom(1);
        List<Long> list = new ArrayList<>();
        list.add(random.nextLong());
        list.add(random.nextLong());
        UUID uuid1 = UuidUtil.setVersion(new UUID(list.get(0), list.get(1)), 4);
        RandomBasedFactory factory = new RandomBasedFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        assertEquals(uuid1, uuid2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123_456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = new UUID(0x0000000000004123L, 0xbedcba9876543210L);
        PrefixCombFactory factory = new PrefixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = "ffffffff-ffff-" + uuid1.toString().substring(14);
        String substring2 = "ffffffff-ffff-" + uuid2.toString().substring(14);
        assertEquals(substring1, substring2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedc_ba9876543210L);
        UUID uuid1 = new UUID(0x0123456789ab4defL, 0xbedcffffffffffffL);
        SuffixCombFactory factory = new SuffixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(0, 24) + "ffffffffffff";
        String substring2 = uuid2.toString().substring(0, 24) + "ffffffffffff";
        assertEquals(substring1, substring2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789ab_cdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = new UUID(0xffff0123456749abL, 0xbedcba9876543210L);
        ShortPrefixCombFactory factory = new ShortPrefixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(4) + "ffff-ffff-ffff-ffff-ffffffffffff";
        String substring2 = uuid2.toString().substring(4) + "ffff-ffff-ffff-ffff-ffffffffffff";
        assertEquals(substring1, substring2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba987654_3210L);
        UUID uuid1 = new UUID(0x0123456789ab4defL, 0xbedcffffba987654L);
        ShortSuffixCombFactory factory = new ShortSuffixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(0, 24) + "ffff" + uuid1.toString().substring(28, 36);
        String substring2 = uuid2.toString().substring(0, 24) + "ffff" + uuid2.toString().substring(28, 36);
        assertEquals(substring1, substring2);
    }
}

@Test
public void testLongRandomWithFactory() {
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = UuidUtil.setVersion(new UUID(list.get(0), list.get(1)), 4);
        RandomBasedFactory factory = new RandomBasedFactory(() -> list.remove(0));
        UUID uuid = factory.create();
        assertEquals(uuid1, uuid);
    }
    {
        SplittableRandom random = new SplittableRandom(1);
        List<Long> list = new ArrayList<>();
        list.add(random.nextLong());
        list.add(random.nextLong());
        UUID uuid1 = UuidUtil.setVersion(new UUID(list.get(0), list.get(1)), 4);
        RandomBasedFactory factory = new RandomBasedFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        assertEquals(uuid1, uuid2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = new UUID(0x0000000000004defL, 0xbedcba9876543210L);
        PrefixCombFactory factory = new PrefixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(14);
        String substring2 = uuid2.toString().substring(14);
        assertEquals(substring1, substring2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = new UUID(0x0123456789ab4defL, 0xb210000000000000L);
        SuffixCombFactory factory = new SuffixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(0, 24);
        String substring2 = uuid2.toString().substring(0, 24);
        assertEquals(substring1, substring2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = new UUID(0x0000456789ab4defL, 0xbedcba9876543210L);
        ShortPrefixCombFactory factory = new ShortPrefixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(14);
        String substring2 = uuid2.toString().substring(14);
        assertEquals(substring1, substring2);
    }
    {
        List<Long> list = new ArrayList<>();
        list.add(0x0123456789abcdefL);
        list.add(0xfedcba9876543210L);
        UUID uuid1 = new UUID(0x0123456789ab4defL, 0xba98000076543210L);
        ShortSuffixCombFactory factory = new ShortSuffixCombFactory(() -> list.remove(0));
        UUID uuid2 = factory.create();
        String substring1 = uuid1.toString().substring(0, 24) + uuid1.toString().substring(28, 36);
        String substring2 = uuid2.toString().substring(0, 24) + uuid2.toString().substring(28, 36);
        assertEquals(substring1, substring2);
    }
}

@Override
public UUID create() {
    lock.lock();
    try {
        final long time = instantFunction.get().toEpochMilli() / interval;
        final long long1 = this.random.nextLong(8);
        final long long2 = this.random.nextLong(6);
        return make(time, long1, long2);
    } finally {
        lock.unlock();
    }
}

@Override
public UUID create() {
    lock.lock();
    try {
        final long time = timeFunction.getAsLong() / interval;
        if (this.random instanceof SafeRandom) {
            final byte[] bytes = this.random.nextBytes(14);
            final long long1 = ByteUtil.toNumber(bytes, 0, 8);
            final long long2 = ByteUtil.toNumber(bytes, 8, 14);
            return make(time, long1, long2);
        } else {
            final long long1 = this.random.nextLong();
            final long long2 = this.random.nextLong();
            return make(time, long1, long2);
        }
    } finally {
        lock.unlock();
    }
}


@Test
void stopAtParent() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parentParent = createMavenProject("parentParent");
    MavenProject parent = createMavenProject("parent", parentParent);
    createMavenProject("child", parent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]" }));
}

@Test
void stopAtParent() {
    MavenProject parentParent = createMavenProject("parentParent");
    MavenProject parent = createMavenProject("parent", parentParent);
    createMavenProject("child", parent);
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]" }));
}

@Override
public String createGraph(MavenProject parent) {
    this.graphBuilder.graphName(parent.getArtifactId());
    if (this.includeParentProjects) {
        buildModuleTree(parent, this.graphBuilder);
    }
    Collection<MavenProject> collectedProjects = this.subProjectSupplier.getSubProjects(parent);
    for (MavenProject collectedProject : collectedProjects) {
        if (isPartOfGraph(collectedProject)) {
            this.mavenGraphAdapter.buildDependencyGraph(collectedProject, this.globalFilter, this.graphBuilder);
        }
    }
    Artifact artifact = parent.getArtifact();
    if (this.graphBuilder.isEmpty() && this.globalFilter.include(artifact)) {
        this.graphBuilder.addNode(new DependencyNode(artifact));
    }
    if (this.reduceEdges) {
        this.graphBuilder.reduceEdges();
    }
    return this.graphBuilder.toString();
}

@Override
public String createGraph(MavenProject parent) {
    this.graphBuilder.graphName(parent.getArtifactId());
    if (this.includeParentProjects) {
        buildModuleTree(parent, this.graphBuilder);
    }
    Collection<MavenProject> collectedProjects = this.subProjectSupplier.get();
    for (MavenProject collectedProject : collectedProjects) {
        if (isPartOfGraph(collectedProject)) {
            this.mavenGraphAdapter.buildDependencyGraph(collectedProject, this.globalFilter, this.graphBuilder);
        }
    }
    Artifact artifact = parent.getArtifact();
    if (this.graphBuilder.isEmpty() && this.globalFilter.include(artifact)) {
        this.graphBuilder.addNode(new DependencyNode(artifact));
    }
    if (this.reduceEdges) {
        this.graphBuilder.reduceEdges();
    }
    return this.graphBuilder.toString();
}

@Test
void excludedArtifact() throws Exception {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parent = createMavenProject("parent");
    MavenProject child1 = createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    when(this.globalFilter.include(child1.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("child1")));
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}

@Test
void excludedArtifact() throws Exception {
    MavenProject parent = createMavenProject("parent");
    MavenProject child1 = createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false);
    when(this.globalFilter.include(child1.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("child1")));
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}

@Test
void nestedProjects() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, true);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"", "\"groupId:subParent:jar:version:compile\"", "\"groupId:child2-1:jar:version:compile\"", "\"groupId:child2-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:subParent:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-1:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-2:jar:version:compile\"[style=dotted]" }));
}

@Test
void nestedProjects() {
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, true);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"", "\"groupId:subParent:jar:version:compile\"", "\"groupId:child2-1:jar:version:compile\"", "\"groupId:child2-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:subParent:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-1:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-2:jar:version:compile\"[style=dotted]" }));
}

@Test
void filteredParent() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    when(this.globalFilter.include(subParent.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]" }));
}

@Test
void filteredParent() {
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false);
    when(this.globalFilter.include(subParent.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]" }));
}

@Test
void excludeParentProjects() throws Exception {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, false, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("parent")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child1")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child2")));
    assertThat(this.graphBuilder, emptyGraph());
}

@Test
void excludeParentProjects() throws Exception {
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, false, false);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("parent")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child1")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child2")));
    assertThat(this.graphBuilder, emptyGraph());
}

@Test
void moduleTree() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}

@Test
void moduleTree() {
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}


@Test
public void stopAtParent() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true);
    MavenProject parentParent = createMavenProject("parentParent");
    MavenProject parent = createMavenProject("parent", parentParent);
    createMavenProject("child", parent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]" }));
}

@Test
public void stopAtParent() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parentParent = createMavenProject("parentParent");
    MavenProject parent = createMavenProject("parent", parentParent);
    createMavenProject("child", parent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]" }));
}

@Override
public String createGraph(MavenProject parent) {
    this.graphBuilder.graphName(parent.getArtifactId());
    if (this.includeParentProjects) {
        buildModuleTree(parent, this.graphBuilder);
    }
    Collection<MavenProject> collectedProjects = this.subProjectSupplier.getSubProjects(parent);
    for (MavenProject collectedProject : collectedProjects) {
        if (isPartOfGraph(collectedProject)) {
            this.mavenGraphAdapter.buildDependencyGraph(collectedProject, this.globalFilter, this.graphBuilder);
        }
    }
    Artifact artifact = parent.getArtifact();
    if (this.graphBuilder.isEmpty() && this.globalFilter.include(artifact)) {
        this.graphBuilder.addNode(new DependencyNode(artifact));
    }
    return this.graphBuilder.toString();
}

@Override
public String createGraph(MavenProject parent) {
    this.graphBuilder.graphName(parent.getArtifactId());
    if (this.includeParentProjects) {
        buildModuleTree(parent, this.graphBuilder);
    }
    Collection<MavenProject> collectedProjects = this.subProjectSupplier.getSubProjects(parent);
    for (MavenProject collectedProject : collectedProjects) {
        if (isPartOfGraph(collectedProject)) {
            this.mavenGraphAdapter.buildDependencyGraph(collectedProject, this.globalFilter, this.graphBuilder);
        }
    }
    Artifact artifact = parent.getArtifact();
    if (this.graphBuilder.isEmpty() && this.globalFilter.include(artifact)) {
        this.graphBuilder.addNode(new DependencyNode(artifact));
    }
    if (this.reduceEdges) {
        this.graphBuilder.reduceEdges();
    }
    return this.graphBuilder.toString();
}

@Test
public void excludedArtifact() throws Exception {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true);
    MavenProject parent = createMavenProject("parent");
    MavenProject child1 = createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    when(this.globalFilter.include(child1.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("child1")));
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void excludedArtifact() throws Exception {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parent = createMavenProject("parent");
    MavenProject child1 = createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    when(this.globalFilter.include(child1.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("child1")));
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void nestedProjects() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"", "\"groupId:subParent:jar:version:compile\"", "\"groupId:child2-1:jar:version:compile\"", "\"groupId:child2-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:subParent:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-1:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void nestedProjects() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, true);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"", "\"groupId:subParent:jar:version:compile\"", "\"groupId:child2-1:jar:version:compile\"", "\"groupId:child2-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:subParent:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-1:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void filteredParent() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    when(this.globalFilter.include(subParent.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void filteredParent() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1-1", parent);
    createMavenProject("child1-2", parent);
    MavenProject subParent = createMavenProject("subParent", parent);
    createMavenProject("child2-1", subParent);
    createMavenProject("child2-2", subParent);
    when(this.globalFilter.include(subParent.getArtifact())).thenReturn(false);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void excludeParentProjects() throws Exception {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("parent")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child1")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child2")));
    assertThat(this.graphBuilder, emptyGraph());
}

@Test
public void excludeParentProjects() throws Exception {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, false, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    graphFactory.createGraph(parent);
    verify(this.dependenciesResolver, never()).resolve(argThat(projectName("parent")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child1")));
    verify(this.dependenciesResolver).resolve(argThat(projectName("child2")));
    assertThat(this.graphBuilder, emptyGraph());
}

@Test
public void moduleTree() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}

@Test
public void moduleTree() {
    AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, this.collectedProjectSupplier, this.globalFilter, this.graphBuilder, true, false);
    MavenProject parent = createMavenProject("parent");
    createMavenProject("child1", parent);
    createMavenProject("child2", parent);
    graphFactory.createGraph(parent);
    assertThat(this.graphBuilder, hasNodesAndEdges(new String[] { "\"groupId:parent:jar:version:compile\"", "\"groupId:child1:jar:version:compile\"", "\"groupId:child2:jar:version:compile\"" }, new String[] { "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]" }));
}


@Test
void requestBackendsTest() throws InterruptedException {
    MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8600/proxy/xservice/ybiz").build();
    MockServerWebExchange exchange = MockServerWebExchange.from(request);
    HttpHeaders headers = new HttpHeaders();
    headers.add("h1", "v1");
    DataBuffer body = null;
    CallbackConfig callbackConfig = new CallbackConfig();
    callbackConfig.receivers = new ArrayList<>();
    Receiver r1 = new Receiver();
    r1.service = "s1";
    r1.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r1.path = "p1";
    callbackConfig.receivers.add(r1);
    Receiver r2 = new Receiver();
    r2.service = "s2";
    r2.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r2.path = "p2";
    callbackConfig.receivers.add(r2);
    ServerHttpRequest req = exchange.getRequest();
    String reqId = req.getId();
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s1", "p1", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.GONE, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s1-rsv-value").body("s1 resp").build()));
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s2", "p2", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s2-rsv-value").body("s2 resp").build()));
    Mono<Void> vm = callbackService.requestBackends(exchange, headers, body, callbackConfig, Collections.EMPTY_MAP);
    vm.subscribe();
    Thread.sleep(2000);
}

@Test
void requestBackendsTest() throws InterruptedException {
    MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8600/proxy/xservice/ybiz").build();
    MockServerWebExchange exchange = MockServerWebExchange.from(request);
    HttpHeaders headers = new HttpHeaders();
    headers.add("h1", "v1");
    DataBuffer body = null;
    CallbackConfig callbackConfig = new CallbackConfig();
    callbackConfig.receivers = new ArrayList<>();
    Receiver r1 = new Receiver();
    r1.service = "s1";
    r1.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r1.path = "p1";
    callbackConfig.receivers.add(r1);
    Receiver r2 = new Receiver();
    r2.service = "s2";
    r2.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r2.path = "p2";
    callbackConfig.receivers.add(r2);
    ServerHttpRequest req = exchange.getRequest();
    String reqId = req.getId();
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s1", "p1", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.GONE, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s1-rsv-value").body("s1 resp").build()));
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s2", "p2", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s2-rsv-value").body("s2 resp").build()));
    Mono<Void> vm = callbackService.requestBackends(exchange, headers, null, callbackConfig, Collections.EMPTY_MAP);
    vm.subscribe();
    Thread.sleep(2000);
}

public Mono<Void> requestBackends(ServerWebExchange exchange, HttpHeaders headers, DataBuffer body, CallbackConfig cc, Map<String, ServiceInstance> service2instMap) {
    ServerHttpRequest req = exchange.getRequest();
    String traceId = WebUtils.getTraceId(exchange);
    HttpMethod method = req.getMethod();
    if (log.isDebugEnabled()) {
        org.apache.logging.log4j.ThreadContext.put(Consts.TRACE_ID, traceId);
        log.debug(traceId + " service2instMap: " + JacksonUtils.writeValueAsString(service2instMap));
    }
    int rs = cc.receivers.size();
    Mono<Object>[] sends = new Mono[rs];
    for (int i = 0; i < rs; i++) {
        Receiver r = cc.receivers.get(i);
        Mono send;
        if (r.type == ApiConfig.Type.SERVICE_DISCOVERY) {
            ServiceInstance si = service2instMap.get(r.service);
            if (si == null) {
                send = fizzWebClient.send2service(traceId, method, r.service, r.path, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            } else {
                String uri = buildUri(req, si, r.path);
                send = fizzWebClient.send(traceId, method, uri, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            }
        } else {
            send = aggregateService.request(WebUtils.getTraceId(exchange), WebUtils.getClientReqPathPrefix(exchange), method.name(), r.service, r.path, req.getQueryParams(), headers, body).onErrorResume(arError(exchange, r, method, headers, body));
        }
        sends[i] = send;
    }
    return Flux.mergeSequential(sends).collectList().flatMap(sendResults -> {
        Object r = null;
        for (int i = 1; i < sendResults.size(); i++) {
            r = sendResults.get(i);
            if (r instanceof ClientResponse && !(r instanceof FizzFailClientResponse)) {
                clean((ClientResponse) r);
            }
        }
        r = sendResults.get(0);
        Throwable t = null;
        if (r instanceof FizzFailClientResponse) {
            t = ((FizzFailClientResponse) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        }
        if (r instanceof FailAggregateResult) {
            t = ((FailAggregateResult) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        } else if (r instanceof ClientResponse) {
            return genServerResponse(exchange, (ClientResponse) r);
        } else {
            return aggregateService.genAggregateResponse(exchange, (AggregateResult) r);
        }
    });
}

public Mono<Void> requestBackends(ServerWebExchange exchange, HttpHeaders headers, String body, CallbackConfig cc, Map<String, ServiceInstance> service2instMap) {
    ServerHttpRequest req = exchange.getRequest();
    String traceId = WebUtils.getTraceId(exchange);
    HttpMethod method = req.getMethod();
    if (log.isDebugEnabled()) {
        org.apache.logging.log4j.ThreadContext.put(Consts.TRACE_ID, traceId);
        log.debug(traceId + " service2instMap: " + JacksonUtils.writeValueAsString(service2instMap));
    }
    int rs = cc.receivers.size();
    Mono<Object>[] sends = new Mono[rs];
    for (int i = 0; i < rs; i++) {
        Receiver r = cc.receivers.get(i);
        Mono send;
        if (r.type == ApiConfig.Type.SERVICE_DISCOVERY) {
            ServiceInstance si = service2instMap.get(r.service);
            if (si == null) {
                send = fizzWebClient.send2service(traceId, method, r.service, r.path, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            } else {
                String uri = buildUri(req, si, r.path);
                send = fizzWebClient.send(traceId, method, uri, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            }
        } else {
            send = aggregateService.request(WebUtils.getTraceId(exchange), WebUtils.getClientReqPathPrefix(exchange), method.name(), r.service, r.path, req.getQueryParams(), headers, body).onErrorResume(arError(exchange, r, method, headers, body));
        }
        sends[i] = send;
    }
    return Flux.mergeSequential(sends).collectList().flatMap(sendResults -> {
        Object r = null;
        for (int i = 1; i < sendResults.size(); i++) {
            r = sendResults.get(i);
            if (r instanceof ClientResponse && !(r instanceof FizzFailClientResponse)) {
                clean((ClientResponse) r);
            }
        }
        r = sendResults.get(0);
        Throwable t = null;
        if (r instanceof FizzFailClientResponse) {
            t = ((FizzFailClientResponse) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        }
        if (r instanceof FailAggregateResult) {
            t = ((FailAggregateResult) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        } else if (r instanceof ClientResponse) {
            return genServerResponse(exchange, (ClientResponse) r);
        } else {
            return aggregateService.genAggregateResponse(exchange, (AggregateResult) r);
        }
    });
}


@Test
void requestBackendsTest() throws InterruptedException {
    MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8600/proxy/xservice/ybiz").build();
    MockServerWebExchange exchange = MockServerWebExchange.from(request);
    HttpHeaders headers = new HttpHeaders();
    headers.add("h1", "v1");
    String body = null;
    CallbackConfig callbackConfig = new CallbackConfig();
    callbackConfig.receivers = new ArrayList<>();
    Receiver r1 = new Receiver();
    r1.service = "s1";
    r1.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r1.path = "p1";
    callbackConfig.receivers.add(r1);
    Receiver r2 = new Receiver();
    r2.service = "s2";
    r2.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r2.path = "p2";
    callbackConfig.receivers.add(r2);
    ServerHttpRequest req = exchange.getRequest();
    String reqId = req.getId();
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s1", "p1", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.GONE, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s1-rsv-value").body("s1 resp").build()));
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s2", "p2", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s2-rsv-value").body("s2 resp").build()));
    Mono<Void> vm = callbackService.requestBackends(exchange, headers, body, callbackConfig, Collections.EMPTY_MAP);
    vm.subscribe();
    Thread.sleep(2000);
}


@Test
void requestBackendsTest() throws InterruptedException {
    MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8600/proxy/xservice/ybiz").build();
    MockServerWebExchange exchange = MockServerWebExchange.from(request);
    HttpHeaders headers = new HttpHeaders();
    headers.add("h1", "v1");
    String body = null;
    CallbackConfig callbackConfig = new CallbackConfig();
    callbackConfig.receivers = new ArrayList<>();
    Receiver r1 = new Receiver();
    r1.service = "s1";
    r1.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r1.path = "p1";
    callbackConfig.receivers.add(r1);
    Receiver r2 = new Receiver();
    r2.service = "s2";
    r2.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r2.path = "p2";
    callbackConfig.receivers.add(r2);
    ServerHttpRequest req = exchange.getRequest();
    String reqId = req.getId();
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s1", "p1", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.GONE, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s1-rsv-value").body("s1 resp").build()));
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s2", "p2", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s2-rsv-value").body("s2 resp").build()));
    Mono<Void> vm = callbackService.requestBackends(exchange, headers, body, callbackConfig, Collections.EMPTY_MAP);
    vm.subscribe();
    Thread.sleep(2000);
}

@Test
void requestBackendsTest() throws InterruptedException {
    MockServerHttpRequest request = MockServerHttpRequest.get("http://127.0.0.1:8600/proxy/xservice/ybiz").build();
    MockServerWebExchange exchange = MockServerWebExchange.from(request);
    HttpHeaders headers = new HttpHeaders();
    headers.add("h1", "v1");
    DataBuffer body = null;
    CallbackConfig callbackConfig = new CallbackConfig();
    callbackConfig.receivers = new ArrayList<>();
    Receiver r1 = new Receiver();
    r1.service = "s1";
    r1.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r1.path = "p1";
    callbackConfig.receivers.add(r1);
    Receiver r2 = new Receiver();
    r2.service = "s2";
    r2.type = ApiConfig.Type.SERVICE_DISCOVERY;
    r2.path = "p2";
    callbackConfig.receivers.add(r2);
    ServerHttpRequest req = exchange.getRequest();
    String reqId = req.getId();
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s1", "p1", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.GONE, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s1-rsv-value").body("s1 resp").build()));
    when(mockFizzWebClient.send2service(reqId, HttpMethod.GET, "s2", "p2", headers, body)).thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND, ExchangeStrategies.withDefaults()).header("FIZZ-RSV", "s2-rsv-value").body("s2 resp").build()));
    Mono<Void> vm = callbackService.requestBackends(exchange, headers, body, callbackConfig, Collections.EMPTY_MAP);
    vm.subscribe();
    Thread.sleep(2000);
}

public Mono<Void> requestBackends(ServerWebExchange exchange, HttpHeaders headers, String body, CallbackConfig cc, Map<String, ServiceInstance> service2instMap) {
    ServerHttpRequest req = exchange.getRequest();
    String traceId = WebUtils.getTraceId(exchange);
    HttpMethod method = req.getMethod();
    if (log.isDebugEnabled()) {
        org.apache.logging.log4j.ThreadContext.put(Consts.TRACE_ID, traceId);
        log.debug(traceId + " service2instMap: " + JacksonUtils.writeValueAsString(service2instMap));
    }
    int rs = cc.receivers.size();
    Mono<Object>[] sends = new Mono[rs];
    for (int i = 0; i < rs; i++) {
        Receiver r = cc.receivers.get(i);
        Mono send;
        if (r.type == ApiConfig.Type.SERVICE_DISCOVERY) {
            ServiceInstance si = service2instMap.get(r.service);
            if (si == null) {
                send = fizzWebClient.send2service(traceId, method, r.service, r.path, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            } else {
                String uri = buildUri(req, si, r.path);
                send = fizzWebClient.send(traceId, method, uri, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            }
        } else {
            send = aggregateService.request(WebUtils.getTraceId(exchange), WebUtils.getClientReqPathPrefix(exchange), method.name(), r.service, r.path, req.getQueryParams(), headers, body).onErrorResume(arError(exchange, r, method, headers, body));
        }
        sends[i] = send;
    }
    return Flux.mergeSequential(sends).collectList().flatMap(sendResults -> {
        Object r = null;
        for (int i = 1; i < sendResults.size(); i++) {
            r = sendResults.get(i);
            if (r instanceof ClientResponse && !(r instanceof FizzFailClientResponse)) {
                clean((ClientResponse) r);
            }
        }
        r = sendResults.get(0);
        Throwable t = null;
        if (r instanceof FizzFailClientResponse) {
            t = ((FizzFailClientResponse) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        }
        if (r instanceof FailAggregateResult) {
            t = ((FailAggregateResult) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        } else if (r instanceof ClientResponse) {
            return genServerResponse(exchange, (ClientResponse) r);
        } else {
            return aggregateService.genAggregateResponse(exchange, (AggregateResult) r);
        }
    });
}

public Mono<Void> requestBackends(ServerWebExchange exchange, HttpHeaders headers, DataBuffer body, CallbackConfig cc, Map<String, ServiceInstance> service2instMap) {
    ServerHttpRequest req = exchange.getRequest();
    String traceId = WebUtils.getTraceId(exchange);
    HttpMethod method = req.getMethod();
    if (log.isDebugEnabled()) {
        org.apache.logging.log4j.ThreadContext.put(Consts.TRACE_ID, traceId);
        log.debug(traceId + " service2instMap: " + JacksonUtils.writeValueAsString(service2instMap));
    }
    int rs = cc.receivers.size();
    Mono<Object>[] sends = new Mono[rs];
    for (int i = 0; i < rs; i++) {
        Receiver r = cc.receivers.get(i);
        Mono send;
        if (r.type == ApiConfig.Type.SERVICE_DISCOVERY) {
            ServiceInstance si = service2instMap.get(r.service);
            if (si == null) {
                send = fizzWebClient.send2service(traceId, method, r.service, r.path, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            } else {
                String uri = buildUri(req, si, r.path);
                send = fizzWebClient.send(traceId, method, uri, headers, body).onErrorResume(crError(exchange, r, method, headers, body));
            }
        } else {
            send = aggregateService.request(WebUtils.getTraceId(exchange), WebUtils.getClientReqPathPrefix(exchange), method.name(), r.service, r.path, req.getQueryParams(), headers, body).onErrorResume(arError(exchange, r, method, headers, body));
        }
        sends[i] = send;
    }
    return Flux.mergeSequential(sends).collectList().flatMap(sendResults -> {
        Object r = null;
        for (int i = 1; i < sendResults.size(); i++) {
            r = sendResults.get(i);
            if (r instanceof ClientResponse && !(r instanceof FizzFailClientResponse)) {
                clean((ClientResponse) r);
            }
        }
        r = sendResults.get(0);
        Throwable t = null;
        if (r instanceof FizzFailClientResponse) {
            t = ((FizzFailClientResponse) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        }
        if (r instanceof FailAggregateResult) {
            t = ((FailAggregateResult) r).throwable;
            return Mono.error(Utils.runtimeExceptionWithoutStack(t.getMessage()));
        } else if (r instanceof ClientResponse) {
            return genServerResponse(exchange, (ClientResponse) r);
        } else {
            return aggregateService.genAggregateResponse(exchange, (AggregateResult) r);
        }
    });
}


@Test
void permitTest() {
    FlowStat flowStat = new FlowStat(circuitBreakManager);
    long currentTimeWindow = flowStat.currentTimeSlotId();
    MockServerHttpRequest mockServerHttpRequest = MockServerHttpRequest.get("/xxx").build();
    MockServerWebExchange mockServerWebExchange = MockServerWebExchange.from(mockServerHttpRequest);
    String service = "xservice";
    String path = "ypath";
    CircuitBreaker cb = new CircuitBreaker();
    cb.service = service;
    cb.path = path;
    cb.resource = ResourceIdUtils.buildResourceId(null, null, null, service, path);
    cb.breakStrategy = CircuitBreaker.BreakStrategy.TOTAL_ERRORS;
    cb.monitorDuration = 5 * 1000;
    cb.minRequests = 100;
    cb.totalErrorThreshold = 10;
    cb.breakDuration = 5 * 1000;
    cb.resumeStrategy = CircuitBreaker.ResumeStrategy.IMMEDIATE;
    cb.stateStartTime = currentTimeWindow;
    Map<String, CircuitBreaker> circuitBreakerMap = circuitBreakManager.getResource2circuitBreakerMap();
    circuitBreakerMap.put(cb.resource, cb);
    ResourceStat resourceStat = flowStat.getResourceStat(cb.resource);
    TimeSlot timeSlot = resourceStat.getTimeSlot(currentTimeWindow);
    timeSlot.getCompReqs().set(200);
    timeSlot.getErrors().set(11);
    boolean permit = circuitBreakManager.permit(mockServerWebExchange, currentTimeWindow, flowStat, service, path);
    Assertions.assertFalse(permit);
    Assertions.assertEquals(CircuitBreaker.State.OPEN, cb.stateRef.get());
    permit = circuitBreakManager.permit(mockServerWebExchange, currentTimeWindow, flowStat, service, path);
    Assertions.assertFalse(permit);
    Assertions.assertEquals(CircuitBreaker.State.OPEN, timeSlot.getCircuitBreakState().get());
    Assertions.assertEquals(2, timeSlot.getCircuitBreakNum().get());
}

@Test
void permitTest() {
    FlowStat flowStat = new FlowStat(circuitBreakManager);
    long currentTimeWindow = flowStat.currentTimeSlotId();
    MockServerHttpRequest mockServerHttpRequest = MockServerHttpRequest.get("/xxx").build();
    MockServerWebExchange mockServerWebExchange = MockServerWebExchange.from(mockServerHttpRequest);
    String service = "xservice";
    String path = "ypath";
    CircuitBreaker cb = new CircuitBreaker();
    cb.service = service;
    cb.path = path;
    cb.resource = ResourceIdUtils.buildResourceId(null, null, null, service, path);
    cb.breakStrategy = CircuitBreaker.BreakStrategy.TOTAL_ERRORS;
    cb.monitorDuration = 5 * 1000;
    cb.minRequests = 100;
    cb.totalErrorThreshold = 10;
    cb.breakDuration = 5 * 1000;
    cb.resumeStrategy = CircuitBreaker.ResumeStrategy.IMMEDIATE;
    cb.stateStartTime = currentTimeWindow;
    Map<String, CircuitBreaker> circuitBreakerMap = circuitBreakManager.getResource2circuitBreakerMap();
    circuitBreakerMap.put(cb.resource, cb);
    ResourceStat resourceStat = flowStat.getResourceStat(cb.resource);
    TimeSlot timeSlot = resourceStat.getTimeSlot(currentTimeWindow);
    timeSlot.setCompReqs(200);
    timeSlot.setErrors(11);
    boolean permit = circuitBreakManager.permit(mockServerWebExchange, currentTimeWindow, flowStat, service, path);
    Assertions.assertFalse(permit);
    Assertions.assertEquals(CircuitBreaker.State.OPEN, cb.stateRef.get());
    permit = circuitBreakManager.permit(mockServerWebExchange, currentTimeWindow, flowStat, service, path);
    Assertions.assertFalse(permit);
    Assertions.assertEquals(CircuitBreaker.State.OPEN, timeSlot.getCircuitBreakState().get());
    Assertions.assertEquals(2, timeSlot.getCircuitBreakNum());
}

public AtomicLong getCircuitBreakNum() {
    return circuitBreakNum;
}

public int getCircuitBreakNum() {
    return circuitBreakNum;
}


@Test
public void testEncodingUtf8() throws UnsupportedEncodingException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final AtomicReference<String> newLabel = new AtomicReference<String>();
    PrintStream ansi = new PrintStream(new AnsiNoSyncOutputStream(baos, new AnsiProcessor(baos) {

        @Override
        protected void processChangeWindowTitle(String label) {
            newLabel.set(label);
        }
    }, "UTF-8"), true, "UTF-8");
    ansi.print("\033]0;ひらがな\007");
    ansi.flush();
    assertEquals("ひらがな", newLabel.get());
}

@Test
public void testEncodingUtf8() throws UnsupportedEncodingException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final AtomicReference<String> newLabel = new AtomicReference<String>();
    PrintStream ansi = new PrintStream(new AnsiOutputStream(baos, new AnsiProcessor(baos) {

        @Override
        protected void processChangeWindowTitle(String label) {
            newLabel.set(label);
        }
    }, "UTF-8"), true, "UTF-8");
    ansi.print("\033]0;ひらがな\007");
    ansi.flush();
    assertEquals("ひらがな", newLabel.get());
}

@Override
public synchronized void write(int data) throws IOException {
    switch(state) {
        case LOOKING_FOR_FIRST_ESC_CHAR:
            if (data == FIRST_ESC_CHAR) {
                buffer[pos++] = (byte) data;
                state = LOOKING_FOR_SECOND_ESC_CHAR;
            } else {
                out.write(data);
            }
            break;
        case LOOKING_FOR_SECOND_ESC_CHAR:
            buffer[pos++] = (byte) data;
            if (data == SECOND_ESC_CHAR) {
                state = LOOKING_FOR_NEXT_ARG;
            } else if (data == SECOND_OSC_CHAR) {
                state = LOOKING_FOR_OSC_COMMAND;
            } else if (data == SECOND_CHARSET0_CHAR) {
                options.add(0);
                state = LOOKING_FOR_CHARSET;
            } else if (data == SECOND_CHARSET1_CHAR) {
                options.add(1);
                state = LOOKING_FOR_CHARSET;
            } else {
                reset(false);
            }
            break;
        case LOOKING_FOR_NEXT_ARG:
            buffer[pos++] = (byte) data;
            if ('"' == data) {
                startOfValue = pos - 1;
                state = LOOKING_FOR_STR_ARG_END;
            } else if ('0' <= data && data <= '9') {
                startOfValue = pos - 1;
                state = LOOKING_FOR_INT_ARG_END;
            } else if (';' == data) {
                options.add(null);
            } else if ('?' == data) {
                options.add('?');
            } else if ('=' == data) {
                options.add('=');
            } else {
                reset(ap.processEscapeCommand(options, data));
            }
            break;
        default:
            break;
        case LOOKING_FOR_INT_ARG_END:
            buffer[pos++] = (byte) data;
            if (!('0' <= data && data <= '9')) {
                String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
                Integer value = Integer.valueOf(strValue);
                options.add(value);
                if (data == ';') {
                    state = LOOKING_FOR_NEXT_ARG;
                } else {
                    reset(ap.processEscapeCommand(options, data));
                }
            }
            break;
        case LOOKING_FOR_STR_ARG_END:
            buffer[pos++] = (byte) data;
            if ('"' != data) {
                String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
                options.add(value);
                if (data == ';') {
                    state = LOOKING_FOR_NEXT_ARG;
                } else {
                    reset(ap.processEscapeCommand(options, data));
                }
            }
            break;
        case LOOKING_FOR_OSC_COMMAND:
            buffer[pos++] = (byte) data;
            if ('0' <= data && data <= '9') {
                startOfValue = pos - 1;
                state = LOOKING_FOR_OSC_COMMAND_END;
            } else {
                reset(false);
            }
            break;
        case LOOKING_FOR_OSC_COMMAND_END:
            buffer[pos++] = (byte) data;
            if (';' == data) {
                String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
                Integer value = Integer.valueOf(strValue);
                options.add(value);
                startOfValue = pos;
                state = LOOKING_FOR_OSC_PARAM;
            } else if ('0' <= data && data <= '9') {
            } else {
                reset(false);
            }
            break;
        case LOOKING_FOR_OSC_PARAM:
            buffer[pos++] = (byte) data;
            if (BEL == data) {
                String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, Charset.defaultCharset());
                options.add(value);
                reset(ap.processOperatingSystemCommand(options));
            } else if (FIRST_ESC_CHAR == data) {
                state = LOOKING_FOR_ST;
            } else {
            }
            break;
        case LOOKING_FOR_ST:
            buffer[pos++] = (byte) data;
            if (SECOND_ST_CHAR == data) {
                String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, Charset.defaultCharset());
                options.add(value);
                reset(ap.processOperatingSystemCommand(options));
            } else {
                state = LOOKING_FOR_OSC_PARAM;
            }
            break;
        case LOOKING_FOR_CHARSET:
            options.add((char) data);
            reset(ap.processCharsetSelect(options));
            break;
    }
    if (pos >= buffer.length) {
        reset(false);
    }
}

@Override
public void write(int data) throws IOException {
    switch(state) {
        case LOOKING_FOR_FIRST_ESC_CHAR:
            if (data == FIRST_ESC_CHAR) {
                buffer[pos++] = (byte) data;
                state = LOOKING_FOR_SECOND_ESC_CHAR;
            } else {
                out.write(data);
            }
            break;
        case LOOKING_FOR_SECOND_ESC_CHAR:
            buffer[pos++] = (byte) data;
            if (data == SECOND_ESC_CHAR) {
                state = LOOKING_FOR_NEXT_ARG;
            } else if (data == SECOND_OSC_CHAR) {
                state = LOOKING_FOR_OSC_COMMAND;
            } else if (data == SECOND_CHARSET0_CHAR) {
                options.add(0);
                state = LOOKING_FOR_CHARSET;
            } else if (data == SECOND_CHARSET1_CHAR) {
                options.add(1);
                state = LOOKING_FOR_CHARSET;
            } else {
                reset(false);
            }
            break;
        case LOOKING_FOR_NEXT_ARG:
            buffer[pos++] = (byte) data;
            if ('"' == data) {
                startOfValue = pos - 1;
                state = LOOKING_FOR_STR_ARG_END;
            } else if ('0' <= data && data <= '9') {
                startOfValue = pos - 1;
                state = LOOKING_FOR_INT_ARG_END;
            } else if (';' == data) {
                options.add(null);
            } else if ('?' == data) {
                options.add('?');
            } else if ('=' == data) {
                options.add('=');
            } else {
                reset(ap.processEscapeCommand(options, data));
            }
            break;
        default:
            break;
        case LOOKING_FOR_INT_ARG_END:
            buffer[pos++] = (byte) data;
            if (!('0' <= data && data <= '9')) {
                String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue);
                Integer value = Integer.valueOf(strValue);
                options.add(value);
                if (data == ';') {
                    state = LOOKING_FOR_NEXT_ARG;
                } else {
                    reset(ap.processEscapeCommand(options, data));
                }
            }
            break;
        case LOOKING_FOR_STR_ARG_END:
            buffer[pos++] = (byte) data;
            if ('"' != data) {
                String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, cs);
                options.add(value);
                if (data == ';') {
                    state = LOOKING_FOR_NEXT_ARG;
                } else {
                    reset(ap.processEscapeCommand(options, data));
                }
            }
            break;
        case LOOKING_FOR_OSC_COMMAND:
            buffer[pos++] = (byte) data;
            if ('0' <= data && data <= '9') {
                startOfValue = pos - 1;
                state = LOOKING_FOR_OSC_COMMAND_END;
            } else {
                reset(false);
            }
            break;
        case LOOKING_FOR_OSC_COMMAND_END:
            buffer[pos++] = (byte) data;
            if (';' == data) {
                String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue);
                Integer value = Integer.valueOf(strValue);
                options.add(value);
                startOfValue = pos;
                state = LOOKING_FOR_OSC_PARAM;
            } else if ('0' <= data && data <= '9') {
            } else {
                reset(false);
            }
            break;
        case LOOKING_FOR_OSC_PARAM:
            buffer[pos++] = (byte) data;
            if (BEL == data) {
                String value = new String(buffer, startOfValue, (pos - 1) - startOfValue, cs);
                options.add(value);
                reset(ap.processOperatingSystemCommand(options));
            } else if (FIRST_ESC_CHAR == data) {
                state = LOOKING_FOR_ST;
            } else {
            }
            break;
        case LOOKING_FOR_ST:
            buffer[pos++] = (byte) data;
            if (SECOND_ST_CHAR == data) {
                String value = new String(buffer, startOfValue, (pos - 2) - startOfValue, cs);
                options.add(value);
                reset(ap.processOperatingSystemCommand(options));
            } else {
                state = LOOKING_FOR_OSC_PARAM;
            }
            break;
        case LOOKING_FOR_CHARSET:
            options.add((char) data);
            reset(ap.processCharsetSelect(options));
            break;
    }
    if (pos >= buffer.length) {
        reset(false);
    }
}

@Test
public void testEncoding8859() throws UnsupportedEncodingException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final AtomicReference<String> newLabel = new AtomicReference<String>();
    PrintStream ansi = new PrintStream(new AnsiNoSyncOutputStream(baos, new AnsiProcessor(baos) {

        @Override
        protected void processChangeWindowTitle(String label) {
            newLabel.set(label);
        }
    }, "ISO-8859-1"), true, "ISO-8859-1");
    ansi.print("\033]0;un bon café\007");
    ansi.flush();
    assertEquals("un bon café", newLabel.get());
}

@Test
public void testEncoding8859() throws UnsupportedEncodingException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final AtomicReference<String> newLabel = new AtomicReference<String>();
    PrintStream ansi = new PrintStream(new AnsiOutputStream(baos, new AnsiProcessor(baos) {

        @Override
        protected void processChangeWindowTitle(String label) {
            newLabel.set(label);
        }
    }, "ISO-8859-1"), true, "ISO-8859-1");
    ansi.print("\033]0;un bon café\007");
    ansi.flush();
    assertEquals("un bon café", newLabel.get());
}


@Test
public void testGlobalComment() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn("1");
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitComments("1", "1", "pending", "master", null, null)).thenReturn(null);
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    facade.addGlobalComment("nothing");
    verify(gitLabAPICommits).postCommitComments(1, "1", "nothing", null, null, null);
}

@Test
public void testGlobalComment() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1"));
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitComments("1", "1", "pending", "master", null, null)).thenReturn(null);
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    facade.addGlobalComment("nothing");
    verify(gitLabAPICommits).postCommitComments(1, "1", "nothing", null, null, null);
}

public void addGlobalComment(String comment) {
    try {
        gitLabAPI.getGitLabAPICommits().postCommitComments(gitLabProject.getId(), config.commitSHA(), comment, null, null, null);
    } catch (IOException e) {
        throw new IllegalStateException("Unable to comment the commit", e);
    }
}

public void addGlobalComment(String comment) {
    try {
        gitLabAPI.getGitLabAPICommits().postCommitComments(gitLabProject.getId(), getFirstCommitSHA(), comment, null, null, null);
    } catch (IOException e) {
        throw new IllegalStateException("Unable to comment the commit", e);
    }
}

@Test
public void testReviewComment() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn("1");
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    File gitBasedir = temp.newFolder();
    facade.setGitBaseDir(gitBasedir);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitComments("1", "1", "pending", "master", null, null)).thenReturn(null);
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    InputFile inputFile = mock(InputFile.class);
    when(inputFile.file()).thenReturn(new File(gitBasedir, "src/main/Foo.java"));
    facade.createOrUpdateReviewComment(inputFile, 5, "nothing");
    verify(gitLabAPICommits).postCommitComments(1, "1", "nothing", "src/main/Foo.java", 5, "new");
}

@Test
public void testReviewComment() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1"));
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    File gitBasedir = temp.newFolder();
    facade.setGitBaseDir(gitBasedir);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitComments("1", "1", "pending", "master", null, null)).thenReturn(null);
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    InputFile inputFile = mock(InputFile.class);
    when(inputFile.file()).thenReturn(new File(gitBasedir, "src/main/Foo.java"));
    facade.createOrUpdateReviewComment(null, inputFile, 5, "nothing");
    verify(gitLabAPICommits).postCommitComments(1, "1", "nothing", "src/main/Foo.java", 5, "new");
}

public void createOrUpdateReviewComment(InputFile inputFile, Integer line, String body) {
    String fullPath = getPath(inputFile);
    try {
        gitLabAPI.getGitLabAPICommits().postCommitComments(gitLabProject.getId(), config.commitSHA(), body, fullPath, line, "new");
    } catch (IOException e) {
        throw new IllegalStateException("Unable to create or update review comment in file " + fullPath + " at line " + line, e);
    }
}

public void createOrUpdateReviewComment(String revision, InputFile inputFile, Integer line, String body) {
    String fullPath = getPath(inputFile);
    try {
        gitLabAPI.getGitLabAPICommits().postCommitComments(gitLabProject.getId(), revision != null ? revision : getFirstCommitSHA(), body, fullPath, line, "new");
    } catch (IOException e) {
        throw new IllegalStateException("Unable to create or update review comment in file " + fullPath + " at line " + line, e);
    }
}

@Test
public void testPatchLineMapping_no_newline_at_the_end() throws IOException {
    Set<Integer> patchLocationMapping = new HashSet<>();
    CommitFacade.processPatch(patchLocationMapping, "@@ -1 +0,0 @@\n-<fake/>\n\\ No newline at end of file");
    assertThat(patchLocationMapping).isEmpty();
}

@Test
public void testPatchLineMapping_no_newline_at_the_end() throws IOException {
    CommitFacade facade = new CommitFacade(mock(GitLabPluginConfiguration.class));
    Set<CommitFacade.Line> patchLocationMapping = facade.getPositionsFromPatch("@@ -1 +0,0 @@\n-<fake/>\n\\ No newline at end of file");
    assertThat(patchLocationMapping).isEmpty();
}

static void processPatch(Set<Integer> patchLocationMapping, String patch) throws IOException {
    int currentLine = -1;
    for (String line : IOUtils.readLines(new StringReader(patch))) {
        if (line.startsWith("@")) {
            Matcher matcher = PATCH_PATTERN.matcher(line);
            if (!matcher.matches()) {
                throw new IllegalStateException("Unable to parse patch line " + line + "\nFull patch: \n" + patch);
            }
            currentLine = Integer.parseInt(matcher.group(1));
        } else if (line.startsWith("-")) {
        } else if (line.startsWith("+") || line.startsWith(" ")) {
            patchLocationMapping.add(currentLine);
            currentLine++;
        } else if (line.startsWith("\\")) {
        }
    }
}

@Test
public void testStatusFailed() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn("1");
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitStatus(1, "1", "pending", "master", "sonarqube", null, "nothing")).thenThrow(new IOException());
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    facade.createOrUpdateSonarQubeStatus("pending", "nothing");
}

@Test
public void testStatusFailed() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1"));
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitStatus(1, "1", "pending", "master", "sonarqube", null, "nothing")).thenThrow(new IOException());
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    facade.createOrUpdateSonarQubeStatus("pending", "nothing");
}

public void createOrUpdateSonarQubeStatus(String status, String statusDescription) {
    try {
        gitLabAPI.getGitLabAPICommits().postCommitStatus(gitLabProject.getId(), config.commitSHA(), status, config.refName(), COMMIT_CONTEXT, null, statusDescription);
    } catch (IOException e) {
        if (e.getMessage() != null && e.getMessage().contains("Cannot transition status")) {
            LOG.debug("Transition status is already {}", status);
        } else {
            LOG.error("Unable to update commit status", e);
        }
    }
}

public void createOrUpdateSonarQubeStatus(String status, String statusDescription) {
    try {
        gitLabAPI.getGitLabAPICommits().postCommitStatus(gitLabProject.getId(), getFirstCommitSHA(), status, config.refName(), COMMIT_CONTEXT, null, statusDescription);
    } catch (IOException e) {
        if (e.getMessage() != null && e.getMessage().contains("Cannot transition status")) {
            LOG.debug("Transition status is already {}", status);
        } else {
            LOG.error("Unable to update commit status", e);
        }
    }
}

@Test
public void testPatchLineMapping_some_deleted_lines() throws IOException {
    Set<Integer> patchLocationMapping = new HashSet<>();
    CommitFacade.processPatch(patchLocationMapping, "@@ -17,9 +17,6 @@\n  * along with this program; if not, write to the Free Software Foundation,\n  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n  */\n-/**\n- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.\n- */\n @ParametersAreNonnullByDefault\n package org.sonar.plugins.core.charts;\n ");
    assertThat(patchLocationMapping).containsOnly(17, 18, 19, 20, 21, 22);
}

@Test
public void testPatchLineMapping_some_deleted_lines() throws IOException {
    CommitFacade facade = new CommitFacade(mock(GitLabPluginConfiguration.class));
    Set<CommitFacade.Line> patchLocationMapping = facade.getPositionsFromPatch("@@ -17,9 +17,6 @@\n  * along with this program; if not, write to the Free Software Foundation,\n  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n  */\n-/**\n- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.\n- */\n @ParametersAreNonnullByDefault\n package org.sonar.plugins.core.charts;\n ");
    assertThat(patchLocationMapping).isEmpty();
}

@Test
public void testGetGitLabUrl() throws Exception {
    File gitBasedir = temp.newFolder();
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn("abc123");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    facade.setGitBaseDir(gitBasedir);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getWebUrl()).thenReturn("https://gitLab.com/gaby/test");
    facade.setGitLabProject(gitLabProject);
    InputPath inputPath = mock(InputPath.class);
    when(inputPath.file()).thenReturn(new File(gitBasedir, "src/main/Foo.java"));
    assertThat(facade.getGitLabUrl(inputPath, 10)).isEqualTo("https://gitLab.com/gaby/test/blob/abc123/src/main/Foo.java#L10");
}

@Test
public void testGetGitLabUrl() throws Exception {
    File gitBasedir = temp.newFolder();
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("abc123"));
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    facade.setGitBaseDir(gitBasedir);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getWebUrl()).thenReturn("https://gitLab.com/gaby/test");
    facade.setGitLabProject(gitLabProject);
    InputPath inputPath = mock(InputPath.class);
    when(inputPath.file()).thenReturn(new File(gitBasedir, "src/main/Foo.java"));
    assertThat(facade.getGitLabUrl(null, inputPath, 10)).isEqualTo("https://gitLab.com/gaby/test/blob/abc123/src/main/Foo.java#L10");
}

@CheckForNull
public String getGitLabUrl(@Nullable InputComponent inputComponent, @Nullable Integer issueLine) {
    if (inputComponent instanceof InputPath) {
        String path = getPath((InputPath) inputComponent);
        return gitLabProject.getWebUrl() + "/blob/" + config.commitSHA() + "/" + path + (issueLine != null ? ("#L" + issueLine) : "");
    }
    return null;
}

@CheckForNull
public String getGitLabUrl(@Nullable String revision, @Nullable InputComponent inputComponent, @Nullable Integer issueLine) {
    if (inputComponent instanceof InputPath) {
        String path = getPath((InputPath) inputComponent);
        return gitLabProject.getWebUrl() + "/blob/" + (revision != null ? revision : getFirstCommitSHA()) + "/" + path + (issueLine != null ? ("#L" + issueLine) : "");
    }
    return null;
}

@Test
public void testPatchLineMapping_some_added_lines() throws IOException {
    Set<Integer> patchLocationMapping = new HashSet<>();
    CommitFacade.processPatch(patchLocationMapping, "@@ -24,9 +24,9 @@\n /**\n  * A plugin is a group of extensions. See org.sonar.api.Extension interface to browse\n  * available extension points.\n- *
\n  *
The manifest property Plugin-Class must declare the name of the implementation class.\n  * It is automatically set by sonar-packaging-maven-plugin when building plugins.
\n+ *
Implementation must declare a public constructor with no-parameters.
\n  *\n  * @see org.sonar.api.Extension\n  * @since 1.10");
    System.out.println(patchLocationMapping);
    assertThat(patchLocationMapping).containsOnly(32, 24, 25, 26, 27, 28, 29, 30, 31);
}

@Test
public void testPatchLineMapping_some_added_lines() throws IOException {
    CommitFacade facade = new CommitFacade(mock(GitLabPluginConfiguration.class));
    Set<CommitFacade.Line> patchLocationMapping = facade.getPositionsFromPatch("@@ -24,9 +24,9 @@\n /**\n  * A plugin is a group of extensions. See org.sonar.api.Extension interface to browse\n  * available extension points.\n- *
\n  *
The manifest property Plugin-Class must declare the name of the implementation class.\n  * It is automatically set by sonar-packaging-maven-plugin when building plugins.
\n+ *
Implementation must declare a public constructor with no-parameters.
\n  *\n  * @see org.sonar.api.Extension\n  * @since 1.10");
    System.out.println(patchLocationMapping);
    assertThat(patchLocationMapping).containsOnly(new CommitFacade.Line(29, " *
Implementation must declare a public constructor with no-parameters.
"));
}

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }
    Line line = (Line) o;
    return Objects.equals(number, line.number) && Objects.equals(content, line.content);
}

@Test
public void testStatusSuccess() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn("1");
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitStatus(1, "1", "pending", "master", "sonarqube", "server", "")).thenReturn(null);
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    facade.createOrUpdateSonarQubeStatus("pending", "nothing");
    verify(gitLabAPICommits).postCommitStatus(1, "1", "pending", "master", "sonarqube", null, "nothing");
}

@Test
public void testStatusSuccess() throws IOException {
    GitLabPluginConfiguration gitLabPluginConfiguration = mock(GitLabPluginConfiguration.class);
    when(gitLabPluginConfiguration.commitSHA()).thenReturn(Collections.singletonList("1"));
    when(gitLabPluginConfiguration.refName()).thenReturn("master");
    CommitFacade facade = new CommitFacade(gitLabPluginConfiguration);
    GitLabAPI gitLabAPI = mock(GitLabAPI.class);
    facade.setGitLabAPI(gitLabAPI);
    GitLabAPICommits gitLabAPICommits = mock(GitLabAPICommits.class);
    when(gitLabAPICommits.postCommitStatus(1, "1", "pending", "master", "sonarqube", "server", "")).thenReturn(null);
    when(gitLabAPI.getGitLabAPICommits()).thenReturn(gitLabAPICommits);
    GitLabProject gitLabProject = mock(GitLabProject.class);
    when(gitLabProject.getId()).thenReturn(1);
    facade.setGitLabProject(gitLabProject);
    facade.createOrUpdateSonarQubeStatus("pending", "nothing");
    verify(gitLabAPICommits).postCommitStatus(1, "1", "pending", "master", "sonarqube", null, "nothing");
}