@Test
public void testAdvisor() throws Exception {
    WorldService worldService = new WorldServiceImpl();
    String expression = "execution(* org.springframework.test.service.WorldService.explode(..))";
    AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
    advisor.setExpression(expression);
    MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice());
    advisor.setAdvice(methodInterceptor);
    ClassFilter classFilter = advisor.getPointcut().getClassFilter();
    if (classFilter.matches(worldService.getClass())) {
        AdvisedSupport advisedSupport = new AdvisedSupport();
        TargetSource targetSource = new TargetSource(worldService);
        advisedSupport.setTargetSource(targetSource);
        advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
        advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
        WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy();
        proxy.explode();
    }
}

@Override
public Advice getAdvice() {
    return advice;
}


@Test
public void testAutowiredAnnotation() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:autowired-annotation.xml");
    Person person = applicationContext.getBean(Person.class);
    assertThat(person.getCar()).isNotNull();
}

public <T> T getBean(Class<T> requiredType) throws BeansException {
    return getBeanFactory().getBean(requiredType);
}


@Test
public void testPointcutExpression() throws Exception {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* org.springframework.test.service.HelloService.*(..))");
    Class<HelloService> clazz = HelloService.class;
    Method method = clazz.getDeclaredMethod("sayHello");
    assertThat(pointcut.matches(clazz)).isTrue();
    assertThat(pointcut.matches(method, clazz)).isTrue();
}

@Override
public boolean matches(Class<?> clazz) {
    return pointcutExpression.couldMatchJoinPointsInType(clazz);
}


@Test
public void testBeforeAdvice() throws Exception {
    WorldServiceBeforeAdvice beforeAdvice = new WorldServiceBeforeAdvice();
    MethodBeforeAdviceInterceptor methodInterceptor = new MethodBeforeAdviceInterceptor(beforeAdvice);
    advisedSupport.setMethodInterceptor(methodInterceptor);
    WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy();
    proxy.explode();
}

public Object getProxy() {
    return createAopProxy().getProxy();
}


@Test
public void testProxyFactory() throws Exception {
    advisedSupport.setProxyTargetClass(false);
    WorldService proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy();
    proxy.explode();
    advisedSupport.setProxyTargetClass(true);
    proxy = (WorldService) new ProxyFactory(advisedSupport).getProxy();
    proxy.explode();
}


@Test
public void testAutoProxy() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml");
    WorldService worldService = applicationContext.getBean("worldService", WorldService.class);
    worldService.explode();
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy);
    if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) {
        return advised.getMethodInterceptor().invoke(methodInvocation);
    }
    return methodInvocation.proceed();
}

@Test
public void testPopulateProxyBeanWithPropertyValues() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:populate-proxy-bean-with-property-values.xml");
    WorldService worldService = applicationContext.getBean("worldService", WorldService.class);
    worldService.explode();
    assertThat(worldService.getName()).isEqualTo("earth");
}

@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    return getBeanFactory().getBean(name, requiredType);
}


public void setTargetSource(TargetSource targetSource) {
    this.targetSource = targetSource;
}

public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
    this.methodInterceptor = methodInterceptor;
}

@Test
public void testJdkDynamicProxy() throws Exception {
    WorldService proxy = (WorldService) new JdkDynamicAopProxy(advisedSupport).getProxy();
    proxy.explode();
}

@Override
public Object getProxy() {
    return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getTargetClass(), this);
}

@Test
public void testCglibDynamicProxy() throws Exception {
    WorldService proxy = (WorldService) new CglibAopProxy(advisedSupport).getProxy();
    proxy.explode();
}

@Override
public Object getProxy() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
    enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
    enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
    return enhancer.create();
}


@Test
public void testEventListener() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:event-and-event-listener.xml");
    applicationContext.publishEvent(new CustomEvent(applicationContext));
    applicationContext.registerShutdownHook();
}

@Override
public void publishEvent(ApplicationEvent event) {
    applicationEventMulticaster.multicastEvent(event);
}


@Test
public void testInitAndDestroyMethod() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:init-and-destroy-method.xml");
    applicationContext.registerShutdownHook();
}

public void registerShutdownHook() {
    Thread shutdownHook = new Thread() {

        public void run() {
            doClose();
        }
    };
    Runtime.getRuntime().addShutdownHook(shutdownHook);
}


@Test
public void testScanPackage() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:package-scan.xml");
    Car car = applicationContext.getBean("car", Car.class);
    assertThat(car).isNotNull();
}


@Test
public void testPrototype() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:prototype-bean.xml");
    Car car1 = applicationContext.getBean("car", Car.class);
    Car car2 = applicationContext.getBean("car", Car.class);
    assertThat(car1 != car2).isTrue();
}


@Test
public void testResourceLoader() throws Exception {
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
    Resource resource = resourceLoader.getResource("classpath:hello.txt");
    InputStream inputStream = resource.getInputStream();
    String content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
    assertThat(content).isEqualTo("hello world");
    resource = resourceLoader.getResource("src/test/resources/hello.txt");
    assertThat(resource instanceof FileSystemResource).isTrue();
    inputStream = resource.getInputStream();
    content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
    assertThat(content).isEqualTo("hello world");
    resource = resourceLoader.getResource("https://github.com/DerekYRC/mini-spring/blob/main/README.md");
    assertThat(resource instanceof UrlResource).isTrue();
    inputStream = resource.getInputStream();
    content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
}

@Override
public Resource getResource(String location) {
    if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
    } else {
        try {
            URL url = new URL(location);
            return new UrlResource(url);
        } catch (MalformedURLException ex) {
            return new FileSystemResource(location);
        }
    }
}


@Test
public void testGenericConversionService() throws Exception {
    GenericConversionService conversionService = new GenericConversionService();
    conversionService.addConverter(new StringToIntegerConverter());
    Integer intNum = conversionService.convert("8888", Integer.class);
    assertThat(conversionService.canConvert(String.class, Integer.class)).isTrue();
    assertThat(intNum).isEqualTo(8888);
    conversionService.addConverterFactory(new StringToNumberConverterFactory());
    assertThat(conversionService.canConvert(String.class, Long.class)).isTrue();
    Long longNum = conversionService.convert("8888", Long.class);
    assertThat(longNum).isEqualTo(8888L);
    conversionService.addConverter(new StringToBooleanConverter());
    assertThat(conversionService.canConvert(String.class, Boolean.class)).isTrue();
    Boolean flag = conversionService.convert("true", Boolean.class);
    assertThat(flag).isTrue();
}

@Override
public <T> T convert(Object source, Class<T> targetType) {
    Class<?> sourceType = source.getClass();
    targetType = (Class<T>) BasicType.wrap(targetType);
    GenericConverter converter = getConverter(sourceType, targetType);
    return (T) converter.convert(source, sourceType, targetType);
}

@Test
public void testStringToNumberConverterFactory() throws Exception {
    StringToNumberConverterFactory converterFactory = new StringToNumberConverterFactory();
    Converter<String, Integer> stringToIntegerConverter = converterFactory.getConverter(Integer.class);
    Integer intNum = stringToIntegerConverter.convert("8888");
    assertThat(intNum).isEqualTo(8888);
    Converter<String, Long> stringToLongConverter = converterFactory.getConverter(Long.class);
    Long longNum = stringToLongConverter.convert("8888");
    assertThat(longNum).isEqualTo(8888L);
}

@Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
    return new StringToNumber<T>(targetType);
}


@Test
public void testBeanPostProcessor() throws Exception {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml");
    CustomerBeanPostProcessor customerBeanPostProcessor = new CustomerBeanPostProcessor();
    beanFactory.addBeanPostProcessor(customerBeanPostProcessor);
    Car car = (Car) beanFactory.getBean("car");
    System.out.println(car);
    assertThat(car.getBrand()).isEqualTo("lamborghini");
}

@Override
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
    this.beanPostProcessors.remove(beanPostProcessor);
    this.beanPostProcessors.add(beanPostProcessor);
}


@Test
public void testApplicationContext() throws Exception {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    Person person = applicationContext.getBean("person", Person.class);
    System.out.println(person);
    assertThat(person.getName()).isEqualTo("ivy");
    Car car = applicationContext.getBean("car", Car.class);
    System.out.println(car);
    assertThat(car.getBrand()).isEqualTo("lamborghini");
}


@Test
public void testReplaceNaNs() {
    Envelope env = new Envelope();
    Point pt = new Point();
    pt.setXY(1, 2);
    pt.setZ(Double.NaN);
    pt.queryEnvelope(env);
    pt.replaceNaNs(VertexDescription.Semantics.Z, 5);
    assertTrue(pt.equals(new Point(1, 2, 5)));
    assertTrue(env.hasZ());
    assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).isEmpty());
    env.replaceNaNs(VertexDescription.Semantics.Z, 5);
    assertTrue(env.queryInterval(VertexDescription.Semantics.Z, 0).equals(new Envelope1D(5, 5)));
}

@Override
public void replaceNaNs(int semantics, double value) {
    addAttribute(semantics);
    if (isEmpty())
        return;
    int ncomps = VertexDescription.getComponentCount(semantics);
    for (int i = 0; i < ncomps; i++) {
        double v = getAttributeAsDbl(semantics, i);
        if (Double.isNaN(v))
            setAttribute(semantics, i, value);
    }
}


@Test
public void testReplaceNaNs() {
    {
        MultiPoint mp = new MultiPoint();
        Point pt = new Point();
        pt.setXY(1, 2);
        pt.setZ(Double.NaN);
        mp.add(pt);
        pt = new Point();
        pt.setXY(11, 12);
        pt.setZ(3);
        mp.add(pt);
        mp.replaceNaNs(VertexDescription.Semantics.Z, 5);
        assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5)));
        assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3)));
    }
    {
        Polygon mp = new Polygon();
        Point pt = new Point();
        pt.setXY(1, 2);
        pt.setZ(Double.NaN);
        mp.startPath(pt);
        pt = new Point();
        pt.setXY(11, 12);
        pt.setZ(3);
        mp.lineTo(pt);
        mp.replaceNaNs(VertexDescription.Semantics.Z, 5);
        assertTrue(mp.getPoint(0).equals(new Point(1, 2, 5)));
        assertTrue(mp.getPoint(1).equals(new Point(11, 12, 3)));
    }
    {
        Polygon mp = new Polygon();
        Point pt = new Point();
        pt.setXY(1, 2);
        pt.setM(Double.NaN);
        mp.startPath(pt);
        pt = new Point();
        pt.setXY(11, 12);
        pt.setM(3);
        mp.lineTo(pt);
        mp.replaceNaNs(VertexDescription.Semantics.M, 5);
        Point p = new Point(1, 2);
        p.setM(5);
        boolean b = mp.getPoint(0).equals(p);
        assertTrue(b);
        p = new Point(11, 12);
        p.setM(3);
        b = mp.getPoint(1).equals(p);
        assertTrue(b);
    }
}

@Override
public void replaceNaNs(int semantics, double value) {
    m_impl.replaceNaNs(semantics, value);
}


@Test
public void testFillRule() throws JsonParseException, IOException {
    MapGeometry mg = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0], [5,10], [10, 0], [0, 7], [10, 7], [0, 0]]]}");
    Polygon poly = (Polygon) mg.getGeometry();
    assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleOddEven);
    poly.setFillRule(Polygon.FillRule.enumFillRuleWinding);
    assertTrue(poly.getFillRule() == Polygon.FillRule.enumFillRuleWinding);
    Geometry simpleResult = OperatorSimplify.local().execute(poly, null, true, null);
    assertTrue(((Polygon) simpleResult).getFillRule() == Polygon.FillRule.enumFillRuleOddEven);
    MapGeometry mg1 = OperatorImportFromJson.local().execute(Geometry.Type.Unknown, "{\"rings\":[[[0,0],[2.5925925925925926,5.185185185185185],[0,7],[3.5,7],[5,10],[6.5,7],[10,7],[7.407407407407407,5.185185185185185],[10,0],[5,3.5],[0,0]]]}");
    boolean equals = OperatorEquals.local().execute(mg1.getGeometry(), simpleResult, null, null);
    assertTrue(equals);
}

public int getFillRule() {
    return m_impl.getFillRule();
}


@Test
public void testEmptyGeometryCollection() {
    SpatialReference sr = SpatialReference.create(4326);
    OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(new ArrayList<OGCGeometry>(), sr);
    assertEquals("{\"type\" : \"GeometryCollection\", \"geometries\" : []}", collection.asGeoJson());
}

@Override
public String asGeoJson() {
    StringBuilder sb = new StringBuilder();
    OperatorExportToGeoJson op = (OperatorExportToGeoJson) OperatorFactoryLocal.getInstance().getOperator(Operator.Type.ExportToGeoJson);
    JsonCursor cursor = op.execute(this.esriSR, getEsriGeometryCursor());
    sb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : ");
    String shape = cursor.next();
    if (shape == null) {
        sb.append("[]}");
        return sb.toString();
    }
    sb.append("[");
    sb.append(shape);
    while (true) {
        shape = cursor.next();
        if (shape == null)
            break;
        sb.append(", ").append(shape);
    }
    sb.append("]}");
    return sb.toString();
}

@Test
public void testGeometryCollection() {
    SpatialReference sr = SpatialReference.create(4326);
    StringBuilder geometrySb = new StringBuilder();
    geometrySb.append("{\"type\" : \"GeometryCollection\", \"geometries\" : [");
    OGCPoint point = new OGCPoint(new Point(1.0, 1.0), sr);
    assertEquals("{\"x\":1,\"y\":1,\"spatialReference\":{\"wkid\":4326}}", point.asJson());
    assertEquals("{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}", point.asGeoJson());
    geometrySb.append(point.asGeoJson()).append(", ");
    OGCLineString line = new OGCLineString(new Polyline(new Point(1.0, 1.0), new Point(2.0, 2.0)), 0, sr);
    assertEquals("{\"paths\":[[[1,1],[2,2]]],\"spatialReference\":{\"wkid\":4326}}", line.asJson());
    assertEquals("{\"type\":\"LineString\",\"coordinates\":[[1.0,1.0],[2.0,2.0]]}", line.asGeoJson());
    geometrySb.append(line.asGeoJson()).append(", ");
    Polygon p = new Polygon();
    p.startPath(1.0, 1.0);
    p.lineTo(2.0, 2.0);
    p.lineTo(3.0, 1.0);
    p.lineTo(2.0, 0.0);
    OGCPolygon polygon = new OGCPolygon(p, sr);
    assertEquals("{\"rings\":[[[1,1],[2,2],[3,1],[2,0],[1,1]]],\"spatialReference\":{\"wkid\":4326}}", polygon.asJson());
    assertEquals("{\"type\":\"Polygon\",\"coordinates\":[[[1.0,1.0],[2.0,2.0],[3.0,1.0],[2.0,0.0],[1.0,1.0]]]}", polygon.asGeoJson());
    geometrySb.append(polygon.asGeoJson()).append("]}");
    List<OGCGeometry> geoms = new ArrayList<OGCGeometry>(3);
    geoms.add(point);
    geoms.add(line);
    geoms.add(polygon);
    OGCConcreteGeometryCollection collection = new OGCConcreteGeometryCollection(geoms, sr);
    assertEquals(geometrySb.toString(), collection.asGeoJson());
}


@Test
void getHostnameIsAbsentWhenNoSSLEngineAndSocketIsPresent() {
    TrustManagerParameters trustManagerParameters = new TrustManagerParameters(null, null, null, null);
    assertThat(trustManagerParameters.getHostname()).isEmpty();
}

public Optional<String> getHostname() {
    return findFirst(getHostnameFromSslEngine(), getHostnameFromSocket());
}

@Test
void getPortIsAbsentWhenNoSSLEngineAndSocketIsPresent() {
    TrustManagerParameters trustManagerParameters = new TrustManagerParameters(null, null, null, null);
    assertThat(trustManagerParameters.getPort()).isEmpty();
}

public Optional<Integer> getPort() {
    return findFirst(getPortFromSslEngine(), getPortFromSocket());
}

@Test
void getHostnameFromSslEngineIfAvailable() {
    SSLEngine sslEngine = mock(SSLEngine.class);
    when(sslEngine.getPeerHost()).thenReturn("localhost");
    TrustManagerParameters trustManagerParameters = new TrustManagerParameters(null, null, null, sslEngine);
    assertThat(trustManagerParameters.getHostname()).hasValue("localhost");
}

@Test
void getPortFromSocketIfAvailable() {
    SSLSocket socket = mock(SSLSocket.class);
    when(socket.getPort()).thenReturn(8443);
    TrustManagerParameters trustManagerParameters = new TrustManagerParameters(null, null, socket, null);
    assertThat(trustManagerParameters.getPort()).hasValue(8443);
}

@Test
void getPortFromSslEngineIfAvailable() {
    SSLEngine sslEngine = mock(SSLEngine.class);
    when(sslEngine.getPeerPort()).thenReturn(8443);
    TrustManagerParameters trustManagerParameters = new TrustManagerParameters(null, null, null, sslEngine);
    assertThat(trustManagerParameters.getPort()).hasValue(8443);
}

@Test
void getHostnameFromSocketIfAvailable() {
    SSLSocket socket = mock(SSLSocket.class);
    InetAddress inetAddress = mock(Inet4Address.class);
    when(inetAddress.getHostName()).thenReturn("localhost");
    when(socket.getInetAddress()).thenReturn(inetAddress);
    TrustManagerParameters trustManagerParameters = new TrustManagerParameters(null, null, socket, null);
    assertThat(trustManagerParameters.getHostname()).hasValue("localhost");
}


@Test
void mapSslFactoryInternalsInFunctionalWay() {
    SSLParameters sslParameters = SSLFactory.builder().withDefaultTrustMaterial().build().map(SSLFactory::getSSLEngine).map(SSLEngine::getSSLParameters).get();
    assertThat(sslParameters).isNotNull();
}

public <T> Box<T> map(Function<SSLFactory, T> mapper) {
    return Box.of(this).map(mapper);
}


@Test
void wrapAnyExceptionInGenericException() {
    Box<String> stringBox = Box.of("").map(value -> {
        throw new RuntimeException("KABOOM");
    });
    assertThatThrownBy(stringBox::get).isInstanceOf(GenericException.class).hasRootCauseMessage("KABOOM");
}

default T get() {
    return valueHolder().get();
}


@Test
public void testApplication() {
    String[] args = {};
    Application.main(args);
}

public static void main(String[] args) {
    Application app = new Application();
    app.createTeam(new YoungTeamFactory());
    LOGGER.info("正在创建一支年轻的队伍...");
    LOGGER.info("-->" + app.getmCaptain().getDescription());
    LOGGER.info("-->" + app.getmShip().getDescription());
    LOGGER.info("-->" + app.getmSailor().getDescription());
    app.createTeam(new PermanentTeamFactory());
    LOGGER.info("正在创建一支久经考验的队伍...");
    LOGGER.info("-->" + app.getmCaptain().getDescription());
    LOGGER.info("-->" + app.getmShip().getDescription());
    LOGGER.info("-->" + app.getmSailor().getDescription());
}


@Test
public void testCreateYoungTeam() {
    mApplication.createTeam(mYoungTeamFactory);
    final Ship ship = mApplication.getmShip();
    final Captain captain = mApplication.getmCaptain();
    final Sailor sailor = mApplication.getmSailor();
    assertTrue(ship instanceof NewShip);
    assertTrue(captain instanceof YoungCaptain);
    assertTrue(sailor instanceof YoungSailor);
    assertEquals(ship.getDescription(), NewShip.DESCRIPTION);
    assertEquals(captain.getDescription(), YoungCaptain.DESCRIPTION);
    assertEquals(sailor.getDescription(), YoungSailor.DESCRIPTION);
}

public void createTeam(final TeamFactory factory) {
    setmCaptain(factory.createCaptain());
    setmShip(factory.createShip());
    setmSailor(factory.createSailor());
}


@Test
public void userServiceState() {
    try {
        Map<String, String> params = new HashMap<>();
        params.put("service_id", "500001");
        params.put("appid", "wxd678efh567hg6787");
        params.put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
        String result = WxPayApi.v3Execution(RequestMethod.GET, WxDomain.CHINA.toString(), WxApiType.USER_SERVICE_STATE.toString(), mchId, serialNo, keyPath, body, params);
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static String v3Execution(RequestMethod method, String urlPrefix, String urlSuffix, String mchId, String serialNo, String keyPath, String body, Map<String, String> params) throws Exception {
    long timestamp = System.currentTimeMillis() / 1000;
    String authType = "WECHATPAY2-SHA256-RSA2048";
    String nonceStr = PayKit.generateStr();
    urlSuffix = urlSuffix.concat("?").concat(PayKit.createLinkString(params, true));
    return v3Execution(method, urlPrefix, urlSuffix, mchId, serialNo, keyPath, body, nonceStr, timestamp, authType);
}


@Test
void testDefaultRenderMethod() {
    ChangedOpenApi diff = new ChangedOpenApi(null);
    assertThat(testRenderImpl.render(diff)).isEqualTo("Output");
}

@Deprecated
default String render(ChangedOpenApi diff) throws RendererException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(byteArrayOutputStream);
    render(diff, outputStreamWriter);
    String result = byteArrayOutputStream.toString();
    try {
        outputStreamWriter.close();
    } catch (IOException e) {
        throw new RendererException(e);
    }
    return result;
}


@Test
public void renderDoesNotCauseStackOverflowWithRecursiveDefinitions() {
    AsciidocRender render = new AsciidocRender();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
    ChangedOpenApi diff = OpenApiCompare.fromLocations("recursive_old.yaml", "recursive_new.yaml");
    render.render(diff, outputStreamWriter);
    assertThat(outputStream.toString()).isNotBlank();
}

@Override
public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
    this.diff = diff;
    if (diff.isUnchanged()) {
        safelyAppend(outputStreamWriter, bigTitle(diff.getNewSpecOpenApi().getInfo().getTitle(), diff.getNewSpecOpenApi().getInfo().getVersion()));
        safelyAppend(outputStreamWriter, System.lineSeparator());
        safelyAppend(outputStreamWriter, System.lineSeparator());
        safelyAppend(outputStreamWriter, "NOTE: No differences. Specifications are equivalents");
    } else {
        safelyAppend(outputStreamWriter, bigTitle(diff.getNewSpecOpenApi().getInfo().getTitle(), diff.getNewSpecOpenApi().getInfo().getVersion()));
        safelyAppend(outputStreamWriter, System.lineSeparator());
        safelyAppend(outputStreamWriter, ":reproducible:\n:sectlinks:\n:toc:\n");
        safelyAppend(outputStreamWriter, System.lineSeparator());
        List<Endpoint> newEndpoints = diff.getNewEndpoints();
        listEndpoints(newEndpoints, "What's New", outputStreamWriter);
        List<Endpoint> missingEndpoints = diff.getMissingEndpoints();
        listEndpoints(missingEndpoints, "What's Deleted", outputStreamWriter);
        List<Endpoint> deprecatedEndpoints = diff.getDeprecatedEndpoints();
        listEndpoints(deprecatedEndpoints, "What's Deprecated", outputStreamWriter);
        List<ChangedOperation> changedOperations = diff.getChangedOperations();
        ol_changed(changedOperations, outputStreamWriter);
        safelyAppend(outputStreamWriter, System.lineSeparator());
        safelyAppend(outputStreamWriter, diff.isCompatible() ? "NOTE: API changes are backward compatible" : "WARNING: API changes broke backward compatibility");
        safelyAppend(outputStreamWriter, System.lineSeparator());
    }
    try {
        outputStreamWriter.close();
    } catch (IOException e) {
        throw new RendererException(e);
    }
}

@Test
public void renderDoesNotFailWhenPropertyHasBeenRemoved() {
    AsciidocRender render = new AsciidocRender();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
    ChangedOpenApi diff = OpenApiCompare.fromLocations("missing_property_1.yaml", "missing_property_2.yaml");
    render.render(diff, outputStreamWriter);
    assertThat(outputStream.toString()).isNotBlank();
}

@Test
public void validateAsciiDocRangeStatus() {
    AsciidocRender render = new AsciidocRender();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
    ChangedOpenApi diff = OpenApiCompare.fromLocations("range_statuscode_1.yaml", "range_statuscode_2.yaml");
    render.render(diff, outputStreamWriter);
    assertThat(outputStream.toString()).isEqualTo("= PROJECTS API (v 1.0.0)\n" + ":reproducible:\n" + ":sectlinks:\n" + ":toc:\n" + "\n" + "== What's Changed\n" + "=== GET   /pet/\n" + "* Return Type:\n" + "** Add 4XX \n" + "** Deleted 405 Method Not Allowed\n" + "\n" + "\n" + "WARNING: API changes broke backward compatibility\n");
}

@Test
public void validateAsciiDocChangeFile() {
    AsciidocRender render = new AsciidocRender();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
    ChangedOpenApi diff = OpenApiCompare.fromLocations("missing_property_1.yaml", "missing_property_2.yaml");
    render.render(diff, outputStreamWriter);
    assertThat(outputStream.toString()).isEqualTo("= TITLE (v 1.0.0)\n" + ":reproducible:\n" + ":sectlinks:\n" + ":toc:\n" + "\n" + "== What's Changed\n" + "=== GET   /\n" + "* Return Type:\n" + "** Changed default \n" + "** Media types:\n" + "*** Changed application/json\n" + "*** Schema:\n" + "Backward compatible\n" + "\n" + "\n" + "NOTE: API changes are backward compatible\n");
}

@Test
public void renderDoesNotFailWhenHTTPStatusCodeIsRange() {
    AsciidocRender render = new AsciidocRender();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
    ChangedOpenApi diff = OpenApiCompare.fromLocations("range_statuscode_1.yaml", "range_statuscode_2.yaml");
    render.render(diff, outputStreamWriter);
    assertThat(outputStream.toString()).isNotBlank();
}


@Test
@DisplayName("return an empty set of books for a non-existent predefined shelf")
void returnEmptySetForNonExistentShelf() {
    String nonExistentShelf = "not a predefined shelf";
    Set<Book> actual = predefinedShelfService.getBooksInChosenPredefinedShelf(nonExistentShelf);
    assertThat(actual).isEmpty();
}

public Set<Book> getBooksInChosenPredefinedShelf(String chosenShelf) {
    Set<Book> books;
    if (isAllBooksShelf(chosenShelf)) {
        return getBooksInAllPredefinedShelves();
    }
    PredefinedShelf.ShelfName predefinedShelfName = getPredefinedShelfName(chosenShelf);
    PredefinedShelf predefinedShelf = findByPredefinedShelfNameAndLoggedInUser(predefinedShelfName);
    if (predefinedShelf == null) {
        books = new HashSet<>();
    } else {
        books = predefinedShelf.getBooks();
    }
    return books;
}

public Set<Book> getBooksInChosenPredefinedShelf(String chosenShelf) {
    if (isAllBooksShelf(chosenShelf)) {
        return getBooksInAllPredefinedShelves();
    }
    PredefinedShelf.ShelfName predefinedShelfName = getPredefinedShelfName(chosenShelf);
    PredefinedShelf predefinedShelf = findByPredefinedShelfNameAndLoggedInUser(predefinedShelfName);
    Set<Book> books;
    if (predefinedShelf == null) {
        books = new HashSet<>();
    } else {
        books = predefinedShelf.getBooks();
    }
    return books;
}


@Test
void testNoProgressMadeTowardsGoal() {
    assertThat(calculateProgressTowardsReadingGoal(5, 0)).isZero();
}

public static double calculateProgressTowardsReadingGoal(int toRead, int read) {
    double progress = (toRead == 0) ? 0 : ((double) read / toRead);
    return Math.min(progress, 1.0);
}

@Test
@DisplayName("Ensure 0, and not an arithmetic exception, is returned")
void shouldNotThrowExceptionOnZeroDivision() {
    int toRead = 5;
    int read = 0;
    SoftAssertions softly = new SoftAssertions();
    softly.assertThatCode(() -> calculateProgressTowardsReadingGoal(toRead, read)).doesNotThrowAnyException();
    softly.assertThat(calculateProgressTowardsReadingGoal(toRead, read)).isZero();
    softly.assertAll();
}

@Test
void testBooksToReadFromStartOfYear() {
    double booksToReadFromStartOfYear = booksToReadFromStartOfYear(26);
    assertThat(booksToReadFromStartOfYear).isEqualTo(0.5);
}

public static double booksToReadFromStartOfYear(int booksToReadThisYear) {
    return ((double) booksToReadThisYear / WEEKS_IN_YEAR);
}

@Test
void calculateCorrectProgressValueWhenGoalMet() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, BOOKS_TO_READ);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValue() {
    int toRead = 25;
    int read = 5;
    double expected = 0.2;
    double actual = calculateProgressTowardsReadingGoal(toRead, read);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValueWhenGoalExceeded() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, (BOOKS_TO_READ + 1));
    assertThat(actual).isEqualTo(expected);
}


@Test
void useUsersCustomShelf_whenCustomShelfIsNull() {
    Book book = createBook(READ, true, "title");
    book.setCustomShelf(null);
    bookForm.setBook(book);
    assertThat(customShelfService.findAllForLoggedInUser().get(0)).isEqualTo(book.getCustomShelf());
}

public void setBook(Book book) {
    if (book == null) {
        LOGGER.log(Level.SEVERE, "Book is null");
        return;
    }
    saveButton.setText(LABEL_UPDATE_BOOK);
    if (binder == null) {
        LOGGER.log(Level.SEVERE, "Null binder");
        return;
    }
    if (book.getCustomShelf() == null) {
        CustomShelf customShelf = customShelfService.createCustomShelf("ShelfName");
        book.setCustomShelf(customShelf);
    }
    binder.setBean(book);
}

public void setBook(Book book) {
    if (book == null) {
        LOGGER.log(Level.SEVERE, "Book is null");
        return;
    }
    saveButton.setText(LABEL_UPDATE_BOOK);
    if (binder == null) {
        LOGGER.log(Level.SEVERE, "Null binder");
        return;
    }
    if (book.getCustomShelf() == null) {
        List<CustomShelf> usersShelves = customShelfService.findAllForLoggedInUser();
        CustomShelf customShelf = !usersShelves.isEmpty() ? usersShelves.get(0) : customShelfService.createCustomShelf("ShelfName");
        book.setCustomShelf(customShelf);
    }
    binder.setBean(book);
}

@Test
void dontResetBinder_whenOpeningFormForExistingBook() {
    Binder<Book> originalBinder = bookForm.binder;
    bookForm.openForm();
    assertThat(bookForm.binder).isEqualTo(originalBinder);
}

public void openForm() {
    clearErrors();
    formDialog.open();
    showSeriesPositionFormIfSeriesPositionAvailable();
    addClassNameToForm();
}

public void openForm() {
    formDialog.open();
    showSeriesPositionFormIfSeriesPositionAvailable();
    addClassNameToForm();
}

@Test
void createNewBinder_whenOpeningFormForNewBook() {
    Binder<Book> originalBinder = bookForm.binder;
    bookForm.addBook();
    assertThat(bookForm.binder).isNotEqualTo(originalBinder);
}

public void addBook() {
    clearFormFields();
    openForm();
}

public void addBook() {
    clearFormFields();
    clearErrors();
    openForm();
}


@Test
@DisplayName("correctly convert into an edition with the 'st' suffix")
@Transactional
void correctlyConvertEditionEndingIn1() {
    int firstEdition = 1;
    int twentyFirstEdition = 21;
    String actualFirst = Book.convertToBookEdition(firstEdition);
    String actualTwentyFirst = Book.convertToBookEdition(twentyFirstEdition);
    assertSoftly(softly -> {
        assertThat(actualFirst).isEqualTo("1st edition");
        assertThat(actualTwentyFirst).isEqualTo("21st edition");
    });
}

public static String convertToBookEdition(int edition) {
    String bookEdition;
    int lastDigit = edition % 10;
    int lastTwoDigits = edition % 100;
    if (lastDigit == 1 && lastTwoDigits != 11) {
        bookEdition = edition + "st edition";
    } else if (lastDigit == 2 && lastTwoDigits != 12) {
        bookEdition = edition + "nd edition";
    } else if (lastDigit == 3 && lastTwoDigits != 13) {
        bookEdition = edition + "rd edition";
    } else {
        bookEdition = edition + "th edition";
    }
    return bookEdition;
}

@Test
@DisplayName("correctly convert into an edition wth the 'rd' suffix")
void correctlyConvertEditionEndingIn3() {
    int thirdEdition = 3;
    int twentyThirdEdition = 23;
    String actualThirdEdition = Book.convertToBookEdition(thirdEdition);
    String actualTwentyThirdEdition = Book.convertToBookEdition(twentyThirdEdition);
    assertSoftly(softly -> {
        softly.assertThat(actualThirdEdition).isEqualTo("3rd edition");
        softly.assertThat(actualTwentyThirdEdition).isEqualTo("23rd edition");
    });
}

@Test
@DisplayName("correctly convert into an edition with the 'th' suffix")
void correctlyConvertNthEdition() {
    int fourthEdition = 4;
    int eleventhEdition = 11;
    String actualFourthEdition = Book.convertToBookEdition(fourthEdition);
    String actualEleventhEdition = Book.convertToBookEdition(eleventhEdition);
    assertSoftly(softly -> {
        softly.assertThat(actualFourthEdition).isEqualTo("4th edition");
        softly.assertThat(actualEleventhEdition).isEqualTo("11th edition");
    });
}

@Test
@DisplayName("correctly convert into an edition with the 'nd' suffix")
void correctlyConvertEditionEndingIn2() {
    int secondEdition = 2;
    int twentySecondEdition = 22;
    String actualSecondEdition = Book.convertToBookEdition(secondEdition);
    String actualTwentySecondEdition = Book.convertToBookEdition(twentySecondEdition);
    assertSoftly(softly -> {
        softly.assertThat(actualSecondEdition).isEqualTo("2nd edition");
        softly.assertThat(actualTwentySecondEdition).isEqualTo("22nd edition");
    });
}


@Test
void getPredefinedShelfNamesCorrectlyAsStrings() {
    List<String> actualShelfNames = predefinedShelfService.getPredefinedShelfNamesAsStrings();
    List<String> expectedShelfNames = Stream.of(PredefinedShelf.ShelfName.values()).map(Enum::toString).collect(Collectors.toList());
    assertSoftly(softly -> {
        softly.assertThat(actualShelfNames).hasSize(expectedShelfNames.size());
        softly.assertThat(expectedShelfNames).containsAll(actualShelfNames);
    });
}

public List<String> getPredefinedShelfNamesAsStrings() {
    return findAllForLoggedInUser().stream().map(Shelf::getShelfName).collect(Collectors.toList());
}


@Test
void showCorrectFormFieldsForDidNotFinishShelf() {
    bookForm.predefinedShelfField.getField().setValue(DID_NOT_FINISH);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.dateStartedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.dateFinishedReadingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.ratingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.bookReviewFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.pagesReadFormItem.isVisible()).isTrue();
    });
}

public T getField() {
    return field;
}

@Test
void notAllowFutureStartDate() {
    bookForm.dateStartedReading.getField().setValue(LocalDate.now().plusDays(5));
    bookForm.saveButton.click();
    BinderValidationStatus<Book> validationStatus = bookForm.binder.validate();
    List<BindingValidationStatus<?>> fieldValidationErrors = validationStatus.getFieldValidationErrors();
    assertSoftly(softly -> {
        softly.assertThat(validationStatus.hasErrors()).isTrue();
        softly.assertThat(fieldValidationErrors).hasSize(1);
        softly.assertThat(fieldValidationErrors.get(0).getMessage().orElseThrow()).isEqualTo(String.format(BookFormErrors.AFTER_TODAY_ERROR, "started"));
    });
}

@Test
void showCorrectFormFieldsForReadingShelf() {
    bookForm.predefinedShelfField.getField().setValue(READING);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.dateStartedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.dateFinishedReadingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.ratingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.bookReviewFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.pagesReadFormItem.isVisible()).isFalse();
    });
}

@Test
void showCorrectFormFieldsForReadShelf() {
    bookForm.predefinedShelfField.getField().setValue(READ);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.dateStartedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.dateFinishedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.ratingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.bookReviewFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.pagesReadFormItem.isVisible()).isFalse();
    });
}

@Test
void displaySeriesPositionWhenIsInSeriesChecked() {
    bookForm.inSeriesCheckbox.getField().setValue(true);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.seriesPositionFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.seriesPosition.getField().isVisible()).isTrue();
        softly.assertThat(SERIES_POSITION).isEqualTo(bookForm.seriesPosition.getValue());
        softly.assertAll();
    });
}


@Test
void notSaveANullGoal() {
    assumeThat(goalService.count()).isOne();
    goalService.save(null);
    assertThat(goalService.count()).isOne();
}

public void save(@NonNull ReadingGoal goal) {
    overwritePreviouslySetGoal(goal);
}

public void save(ReadingGoal goal) {
    if (goal != null) {
        overwritePreviouslySetGoals(goal);
    }
}


@Test
void lowestRatedBookThisYearIsFound() {
    assertThat(yearStatistic.findLeastLikedBookThisYear().get().getTitle()).isEqualTo(bookWithLowestRatingThisYear.getTitle());
}

public Optional<Book> findLeastLikedBookThisYear() {
    if (readBooksThisYear.isEmpty()) {
        return Optional.empty();
    }
    readBooksThisYear.sort(Comparator.comparing(Book::getRating));
    return Optional.of(readBooksThisYear.get(0));
}

@Test
void highestRatedBookExistsAndIsFound() {
    assertThat(yearStatistic.findMostLikedBookThisYear().get().getTitle()).isEqualTo(bookWithHighestRatingThisYear.getTitle());
}

public Optional<Book> findMostLikedBookThisYear() {
    if (readBooksThisYear.isEmpty()) {
        return Optional.empty();
    }
    readBooksThisYear.sort(Comparator.comparing(Book::getRating));
    return Optional.of(readBooksThisYear.get(readBooksThisYear.size() - 1));
}


@Test
void getPredefinedShelfNamesCorrectlyAsStrings() {
    List<String> actualShelfNames = predefinedShelfUtils.getPredefinedShelfNamesAsStrings();
    List<String> expectedShelfNames = Stream.of(ShelfName.values()).map(Enum::toString).collect(Collectors.toList());
    assertSoftly(softly -> {
        softly.assertThat(actualShelfNames).hasSize(expectedShelfNames.size());
        softly.assertThat(expectedShelfNames).containsAll(actualShelfNames);
    });
}

public List<String> getPredefinedShelfNamesAsStrings() {
    return predefinedShelfService.findAllForLoggedInUser().stream().map(Shelf::getShelfName).collect(Collectors.toList());
}


@Test
@DisplayName("Non-orphan authors shouldn't be removed when one of their books is deleted")
void notRemoveNonOrphans() {
    Author nonOrphan = new Author("Jostein", "Gaarder");
    Book book = new Book("Sophie's World", nonOrphan, toRead);
    bookService.save(book);
    bookService.save(new Book("The Other World", nonOrphan, toRead));
    bookService.delete(book);
    assumeThat(bookService.count()).isOne();
    assertThat(authorService.count()).isOne();
}

public void delete(Book book) {
    if (book == null) {
        LOGGER.log(Level.SEVERE, "Cannot delete an null book.");
        return;
    }
    LOGGER.log(Level.INFO, "Deleting book. Book repository size = " + bookRepository.count());
    bookRepository.delete(book);
    List<Book> books = bookRepository.findAll();
    if (books.contains(book)) {
        LOGGER.log(Level.SEVERE, book.getTitle() + " not deleted");
    } else {
        LOGGER.log(Level.INFO, book.getTitle() + " deleted. Book repository size = " + bookRepository.count());
    }
}

public void delete(@NonNull Book book) {
    LOGGER.log(Level.INFO, "Deleting book. Book repository size = " + bookRepository.count());
    bookRepository.delete(book);
    List<Book> books = bookRepository.findAll();
    if (books.contains(book)) {
        LOGGER.log(Level.SEVERE, book.getTitle() + " not deleted");
    } else {
        LOGGER.log(Level.INFO, book.getTitle() + " deleted. Book repository size = " + bookRepository.count());
        Author author = book.getAuthor();
        removeBookFromAuthor(book, author);
        removeAuthorWithoutBooks(author);
    }
}


@Test
void testNoProgressMadeTowardsGoal() {
    assertThat(calculateProgressTowardsReadingGoal(5, 0)).isZero();
}

@Test
@DisplayName("return false if the book in book has finish date this year is null")
void returnFalseInBookHasFinishDateThisYearIfBookIsNull() {
    boolean actual = ReadingGoalCalculator.bookHasFinishDateInThisYear(null);
    assertThat(actual).isFalse();
}

@VisibleForTesting
static boolean bookHasFinishDateInThisYear(Book book) {
    return book != null && book.getDateFinishedReading() != null && DateUtils.dateIsInCurrentYear(book.getDateFinishedReading());
}

@Test
void testBooksToReadFromStartOfYear() {
    int booksToReadThisYear = 26;
    double booksToReadFromStartOfYear = booksToReadFromStartOfYear(booksToReadThisYear);
    double expected = 0.5;
    assertThat(booksToReadFromStartOfYear).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValueWhenGoalMet() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, BOOKS_TO_READ);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValue() {
    int toRead = 25;
    int read = 5;
    double expected = 0.2;
    double actual = calculateProgressTowardsReadingGoal(toRead, read);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValueWhenGoalExceeded() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, (BOOKS_TO_READ + 1));
    assertThat(actual).isEqualTo(expected);
}


@Test
@DisplayName("be able to delete an author without any books")
void deleteAuthorWithoutBooks() {
    assumeThat(authorService.count()).isZero();
    Author authorWithoutBooks = new Author("First", "Last");
    authorService.save(authorWithoutBooks);
    authorService.delete(authorWithoutBooks);
    assertThat(authorService.count()).isZero();
}

public void delete(Author author) {
    authorRepository.delete(author);
}

public void delete(@NonNull Author author) {
    authorRepository.delete(author);
}


@Test
void getPredefinedShelfNamesCorrectlyAsStrings() {
    List<String> actualShelfNames = predefinedShelfService.getPredefinedShelfNamesAsStrings();
    List<String> expectedShelfNames = Stream.of(PredefinedShelf.ShelfName.values()).map(Enum::toString).collect(Collectors.toList());
    assertSoftly(softly -> {
        softly.assertThat(actualShelfNames).hasSize(expectedShelfNames.size());
        softly.assertThat(expectedShelfNames).containsAll(actualShelfNames);
    });
}


@Test
void correctlyCheckIfDateIsInCurrentYear() {
    LocalDate lastDayOfYear = LocalDate.of(MOCK_CURRENT_DATE.getYear(), 12, 31);
    assertThat(DateUtils.dateIsInCurrentYear(MOCK_CURRENT_DATE)).isTrue();
    assertThat(DateUtils.dateIsInCurrentYear(MOCK_CURRENT_DATE.minusDays(1))).isTrue();
    assertThat(DateUtils.dateIsInCurrentYear(lastDayOfYear)).isTrue();
    assertThat(DateUtils.dateIsInCurrentYear(MOCK_CURRENT_DATE.minusYears(1))).isFalse();
    assertThat(DateUtils.dateIsInCurrentYear(MOCK_CURRENT_DATE.plusYears(1))).isFalse();
}

public static boolean dateIsInCurrentYear(@NotNull LocalDate date) {
    return date.getYear() == LocalDate.now().getYear();
}

public static boolean dateIsInCurrentYear(@NotNull LocalDate date) {
    return date.getYear() == TimeUtils.currentDate().getYear();
}

@Test
void correctlyCheckIfDateIsInFuture() {
    assertThat(DateUtils.isDateInFuture(MOCK_CURRENT_DATE.plusYears(1))).isTrue();
    assertThat(DateUtils.isDateInFuture(MOCK_CURRENT_DATE.plusDays(1))).isTrue();
    assertThat(DateUtils.isDateInFuture(MOCK_CURRENT_DATE)).isFalse();
    assertThat(DateUtils.isDateInFuture(MOCK_CURRENT_DATE.minusDays(1))).isFalse();
    assertThat(DateUtils.isDateInFuture(MOCK_CURRENT_DATE.minusMonths(1))).isFalse();
}

public static boolean isDateInFuture(LocalDate date) {
    return date.isAfter(TimeUtils.currentDate());
}


@Test
void showCorrectFormFieldsForDidNotFinishShelf() {
    bookForm.predefinedShelfField.getField().setValue(DID_NOT_FINISH);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.dateStartedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.dateFinishedReadingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.ratingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.bookReviewFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.pagesReadFormItem.isVisible()).isTrue();
    });
}

@Test
void notAllowFutureStartDate() {
    bookForm.dateStartedReading.getField().setValue(LocalDate.now().plusDays(5));
    bookForm.saveButton.click();
    BinderValidationStatus<Book> validationStatus = bookForm.binder.validate();
    List<BindingValidationStatus<?>> fieldValidationErrors = validationStatus.getFieldValidationErrors();
    assertSoftly(softly -> {
        softly.assertThat(validationStatus.hasErrors()).isTrue();
        softly.assertThat(fieldValidationErrors).hasSize(1);
        softly.assertThat(fieldValidationErrors.get(0).getMessage().orElseThrow()).isEqualTo(String.format(BookFormErrors.AFTER_TODAY_ERROR, "started"));
    });
}

@Test
void showCorrectFormFieldsForReadingShelf() {
    bookForm.predefinedShelfField.getField().setValue(READING);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.dateStartedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.dateFinishedReadingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.ratingFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.bookReviewFormItem.isVisible()).isFalse();
        softly.assertThat(bookForm.pagesReadFormItem.isVisible()).isFalse();
    });
}

@Test
void showCorrectFormFieldsForReadShelf() {
    bookForm.predefinedShelfField.getField().setValue(READ);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.dateStartedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.dateFinishedReadingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.ratingFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.bookReviewFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.pagesReadFormItem.isVisible()).isFalse();
    });
}

@Test
void displaySeriesPositionWhenIsInSeriesChecked() {
    bookForm.inSeriesCheckbox.getField().setValue(true);
    assertSoftly(softly -> {
        softly.assertThat(bookForm.seriesPositionFormItem.isVisible()).isTrue();
        softly.assertThat(bookForm.seriesPosition.getField().isVisible()).isTrue();
        softly.assertThat(SERIES_POSITION).isEqualTo(bookForm.seriesPosition.getValue());
        softly.assertAll();
    });
}


@Test
void onlyDisplayRatingUnitOnce() {
    String title = "Harry Potter and the Chamber of Secrets";
    RatingScale rating = RatingScale.NO_RATING;
    String actual = StatisticsView.formatStatistic(title, rating.toString(), "rating");
    String expected = String.format("%s (%s)", title, rating.toString());
    assertThat(actual).isEqualTo(expected);
}

private static String formatStatistic(String bookTitle, String statistic, String unit) {
    return String.format("%s (%s %s)", bookTitle, statistic, unit);
}

@VisibleForTesting
static String formatStatistic(String bookTitle, String statistic, String unit) {
    if (statistic.endsWith(unit)) {
        return String.format("%s (%s)", bookTitle, statistic);
    } else {
        return String.format("%s (%s %s)", bookTitle, statistic, unit);
    }
}

@Test
void includePagesUnitWhenFormatted() {
    String title = "Harry Potter and the Chamber of Secrets";
    int pages = 500;
    String unit = "pages";
    String actual = StatisticsView.formatStatistic(title, String.valueOf(pages), unit);
    String expected = String.format("%s (%s %s)", title, pages, unit);
    assertThat(actual).isEqualTo(expected);
}


@Test
void useUsersCustomShelf_whenCustomShelfIsNull() {
    Book book = createBook(READ, true, "title");
    book.setCustomShelf(null);
    bookForm.setBook(book);
    assertThat(customShelfService.findAllForLoggedInUser().get(0)).isEqualTo(book.getCustomShelf());
}

@Test
void dontResetBinder_whenOpeningFormForExistingBook() {
    Binder<Book> originalBinder = bookForm.binder;
    bookForm.openForm();
    assertThat(bookForm.binder).isEqualTo(originalBinder);
}

@Test
void createNewBinder_whenOpeningFormForNewBook() {
    Binder<Book> originalBinder = bookForm.binder;
    bookForm.addBook();
    assertThat(bookForm.binder).isNotEqualTo(originalBinder);
}


@Test
@DisplayName("correctly convert into an edition with the 'st' suffix")
@Transactional
void correctlyConvertEditionEndingIn1() {
    int firstEdition = 1;
    int twentyFirstEdition = 21;
    String actualFirst = Book.convertToBookEdition(firstEdition);
    String actualTwentyFirst = Book.convertToBookEdition(twentyFirstEdition);
    assertSoftly(softly -> {
        assertThat(actualFirst).isEqualTo("1st edition");
        assertThat(actualTwentyFirst).isEqualTo("21st edition");
    });
}

public static String convertToBookEdition(int edition) {
    String bookEdition = "";
    int lastDigit = edition % 10;
    switch(lastDigit) {
        case 1:
            bookEdition = edition + "st edition";
            break;
        case 2:
            bookEdition = edition + "nd edition";
            break;
        case 3:
            bookEdition = edition + "rd edition";
            break;
        default:
            bookEdition = edition + "th edition";
            break;
    }
    return bookEdition;
}

@Test
@DisplayName("correctly convert into an edition wth the 'rd' suffix")
void correctlyConvertEditionEndingIn3() {
    int thirdEdition = 3;
    int twentyThirdEdition = 23;
    String actualThirdEdition = Book.convertToBookEdition(thirdEdition);
    String actualTwentyThirdEdition = Book.convertToBookEdition(twentyThirdEdition);
    assertSoftly(softly -> {
        softly.assertThat(actualThirdEdition).isEqualTo("3rd edition");
        softly.assertThat(actualTwentyThirdEdition).isEqualTo("23rd edition");
    });
}

@Test
@DisplayName("correctly convert into an edition with the 'th' suffix")
void correctlyConvertNthEdition() {
    int fourthEdition = 4;
    int eleventhEdition = 11;
    String actualFourthEdition = Book.convertToBookEdition(fourthEdition);
    String actualEleventhEdition = Book.convertToBookEdition(eleventhEdition);
    assertSoftly(softly -> {
        softly.assertThat(actualFourthEdition).isEqualTo("4th edition");
        softly.assertThat(actualEleventhEdition).isEqualTo("11th edition");
    });
}

@Test
@DisplayName("correctly convert into an edition with the 'nd' suffix")
void correctlyConvertEditionEndingIn2() {
    int secondEdition = 2;
    int twentySecondEdition = 22;
    String actualSecondEdition = Book.convertToBookEdition(secondEdition);
    String actualTwentySecondEdition = Book.convertToBookEdition(twentySecondEdition);
    assertSoftly(softly -> {
        softly.assertThat(actualSecondEdition).isEqualTo("2nd edition");
        softly.assertThat(actualTwentySecondEdition).isEqualTo("22nd edition");
    });
}


@Test
@DisplayName("Ensure 0, and not an arithmetic exception, is returned")
void shouldNotThrowExceptionOnZeroDivision() {
    int toRead = 5;
    int read = 0;
    SoftAssertions softly = new SoftAssertions();
    softly.assertThatCode(() -> calculateProgressTowardsReadingGoal(toRead, read)).doesNotThrowAnyException();
    softly.assertThat(calculateProgressTowardsReadingGoal(toRead, read)).isZero();
    softly.assertAll();
}

public static double calculateProgressTowardsReadingGoal(int toRead, int read) {
    double progress = (toRead == 0) ? 0 : ((double) read / toRead);
    return Math.min(progress, 1.0);
}

@Test
void calculateCorrectProgressValueWhenGoalMet() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, BOOKS_TO_READ);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValueWhenGoalExceeded() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, (BOOKS_TO_READ + 1));
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValue() {
    int toRead = 25;
    int read = 5;
    double expected = 0.2;
    double actual = calculateProgressTowardsReadingGoal(toRead, read);
    assertThat(actual).isEqualTo(expected);
}

@Test
void testNoProgressMadeTowardsGoal() {
    assertThat(calculateProgressTowardsReadingGoal(5, 0)).isZero();
}

@Test
void testBooksToReadFromStartOfYear() {
    double booksToReadFromStartOfYear = booksToReadFromStartOfYear(26);
    assertThat(booksToReadFromStartOfYear).isEqualTo(0.5);
}

public static double booksToReadFromStartOfYear(int booksToReadThisYear) {
    return ((double) booksToReadThisYear / WEEKS_IN_YEAR);
}


@Test
void notSaveANullTag() {
    assumeThat(tagService.count()).isZero();
    tagService.save(null);
    assertThat(tagService.count()).isZero();
}

public void save(@NonNull Tag tag) {
    tagRepository.save(tag);
}

public void save(Tag tag) {
    if (tag != null) {
        tagRepository.save(tag);
    } else {
        LOGGER.log(Level.SEVERE, "Null tag");
    }
}


@Test
void goodreadsBookImportIsSavedWhenAuthorAndPredefinedShelfIsNotEmpty() {
    GoodreadsBookImport goodreadsBookImport = new GoodreadsBookImport();
    goodreadsBookImport.setTitle("Blink: The Power of Thinking Without Thinking");
    goodreadsBookImport.setAuthor("Malcolm Gladwell");
    goodreadsBookImport.setBookshelves("Reading");
    List<Book> savedBooks = importService.importGoodreadsBooks(Collections.singletonList(goodreadsBookImport));
    int expected = 1;
    assertThat(savedBooks.size()).isEqualTo(expected);
}

public List<Book> importGoodreadsBooks(Collection<? extends GoodreadsBookImport> goodreadsBookImports) {
    List<Book> books = toBooks(goodreadsBookImports);
    List<Book> savedBooks = books.stream().map(bookService::save).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
    savedBooks.forEach(b -> LOGGER.info("Book: " + b + " saved successfully"));
    return savedBooks;
}


@Test
void highestRatedBookExistsAndIsFound() {
    assertEquals(bookWithHighestRatingThisYear.getTitle(), yearStatistic.findMostLikedBookThisYear().getTitle());
}

public Book findMostLikedBookThisYear() {
    if (readBooksThisYear.isEmpty()) {
        return null;
    }
    readBooksThisYear.sort(Comparator.comparing(Book::getRating));
    return readBooksThisYear.get(readBooksThisYear.size() - 1);
}

@Test
void lowestRatedThisYearBookExistsAndIsFound() {
    assertEquals(bookWithLowestRatingThisYear.getTitle(), yearStatistic.findLeastLikedBookThisYear().getTitle());
}

public Book findLeastLikedBookThisYear() {
    if (readBooksThisYear.isEmpty()) {
        return null;
    }
    readBooksThisYear.sort(Comparator.comparing(Book::getRating));
    return readBooksThisYear.get(0);
}


@Test
void getPredefinedShelfNamesCorrectlyAsStrings() {
    List<String> actualShelfNames = predefinedShelfService.getPredefinedShelfNamesAsStrings();
    List<String> expectedShelfNames = Stream.of(PredefinedShelf.ShelfName.values()).map(Enum::toString).collect(Collectors.toList());
    assertSoftly(softly -> {
        softly.assertThat(actualShelfNames).hasSize(expectedShelfNames.size());
        softly.assertThat(expectedShelfNames).containsAll(actualShelfNames);
    });
}

public List<String> getPredefinedShelfNamesAsStrings() {
    return findAllForLoggedInUser().stream().map(Shelf::getShelfName).collect(Collectors.toList());
}


@Test
void getPredefinedShelfByStringName() {
    assertThat(predefinedShelfUtils.findPredefinedShelf("To read")).isNotNull();
    assertThat(predefinedShelfUtils.findPredefinedShelf("Reading")).isNotNull();
    assertThat(predefinedShelfUtils.findPredefinedShelf("Read")).isNotNull();
    assertThat(predefinedShelfUtils.findPredefinedShelf("Did not finish")).isNotNull();
}

public PredefinedShelf findPredefinedShelf(String shelfName) {
    return predefinedShelfService.findAllForLoggedInUser().stream().filter(shelf -> shelf.getPredefinedShelfName().toString().equals(shelfName)).collect(Collectors.toList()).get(0);
}


@Test
void getPredefinedShelfNamesCorrectlyAsStrings() {
    List<String> actualShelfNames = predefinedShelfUtils.getPredefinedShelfNamesAsStrings();
    List<String> expectedShelfNames = Stream.of(ShelfName.values()).map(Enum::toString).collect(Collectors.toList());
    assertSoftly(softly -> {
        softly.assertThat(actualShelfNames).hasSize(expectedShelfNames.size());
        softly.assertThat(expectedShelfNames).containsAll(actualShelfNames);
    });
}


@Test
@DisplayName("Throw exception when saving a new Publisher with existing name")
void throwExceptionWhileSavingDuplicatePublisher() {
    Publisher publisher1 = new Publisher("Test DuplicatePublisher");
    Publisher publisher2 = new Publisher("Test DuplicatePublisher");
    publisherService.save(publisher1);
    assertThatThrownBy(() -> publisherService.save(publisher2)).isInstanceOf(DataIntegrityViolationException.class);
}

public void save(@NonNull Publisher publisher) {
    if (StringUtils.isNotEmpty(publisher.getName())) {
        publisherRepository.save(publisher);
    }
}

@Test
@Transactional
@DisplayName("Delete a Publisher")
void deleteExistingPublisher() {
    Publisher publisher = new Publisher("Test DeletePublisher");
    publisherService.save(publisher);
    Long publisherId = publisher.getId();
    publisherService.delete(publisher);
    Optional<Publisher> deletedPublisher = publisherService.findById(publisherId);
    assertThat(deletedPublisher).isEmpty();
}

public void delete(@NonNull Publisher publisher) {
    publisherRepository.delete(publisher);
}

@Test
void saveValidPublisher() {
    Publisher publisher = new Publisher("Test SavePublisher ");
    publisherService.save(publisher);
    assertThat(publisherService.findById(publisher.getId())).isNotNull();
}

@Test
void findAllSavedPublishers() {
    Publisher publisher1 = new Publisher("Test SavePublisher1");
    Publisher publisher2 = new Publisher("Test SavePublisher2");
    publisherService.save(publisher1);
    publisherService.save(publisher2);
    List<Publisher> allPublishers = publisherService.findAll();
    assertThat(allPublishers).contains(publisher1, publisher2);
}


@Test
void useUsersCustomShelf_whenCustomShelfIsNull() {
    Book book = createBook(READ, true, "title");
    book.setCustomShelf(null);
    bookForm.setBook(book);
    assertThat(customShelfService.findAllForLoggedInUser().get(0)).isEqualTo(book.getCustomShelf());
}

public void setBook(Book book) {
    if (book == null) {
        LOGGER.log(Level.SEVERE, "Book is null");
        return;
    }
    saveButton.setText(LABEL_UPDATE_BOOK);
    if (binder == null) {
        LOGGER.log(Level.SEVERE, "Null binder");
        return;
    }
    if (book.getCustomShelf() == null) {
        List<CustomShelf> usersShelves = customShelfService.findAllForLoggedInUser();
        CustomShelf customShelf = !usersShelves.isEmpty() ? usersShelves.get(0) : customShelfService.createCustomShelf("ShelfName");
        book.setCustomShelf(customShelf);
    }
    binder.setBean(book);
}

@Test
void dontResetBinder_whenOpeningFormForExistingBook() {
    Binder<Book> originalBinder = bookForm.binder;
    bookForm.openForm();
    assertThat(bookForm.binder).isEqualTo(originalBinder);
}

public void openForm() {
    formDialog.open();
    showSeriesPositionFormIfSeriesPositionAvailable();
    addClassNameToForm();
}

@Test
void createNewBinder_whenOpeningFormForNewBook() {
    Binder<Book> originalBinder = bookForm.binder;
    bookForm.addBook();
    assertThat(bookForm.binder).isNotEqualTo(originalBinder);
}

public void addBook() {
    clearFormFields();
    clearErrors();
    openForm();
}


@Test
@DisplayName("Ensure 0, and not an arithmetic exception, is returned")
void shouldNotThrowExceptionOnZeroDivision() {
    int toRead = 5;
    int read = 0;
    SoftAssertions softly = new SoftAssertions();
    softly.assertThatCode(() -> calculateProgressTowardsReadingGoal(toRead, read)).doesNotThrowAnyException();
    softly.assertThat(calculateProgressTowardsReadingGoal(toRead, read)).isZero();
    softly.assertAll();
}

@Test
void calculateCorrectProgressValueWhenGoalMet() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, BOOKS_TO_READ);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValue() {
    int toRead = 25;
    int read = 5;
    double expected = 0.2;
    double actual = calculateProgressTowardsReadingGoal(toRead, read);
    assertThat(actual).isEqualTo(expected);
}

@Test
void calculateCorrectProgressValueWhenGoalExceeded() {
    double expected = 1.0;
    double actual = calculateProgressTowardsReadingGoal(BOOKS_TO_READ, (BOOKS_TO_READ + 1));
    assertThat(actual).isEqualTo(expected);
}


@Test
void findLowestRatedBookThisYear() {
    String actualTitle = yearStatistic.findLeastLikedBookThisYear().get().getTitle();
    String expectedTitle = bookWithLowestRatingThisYear.getTitle();
    assertThat(actualTitle).isEqualTo(expectedTitle);
}

public Optional<Book> findLeastLikedBookThisYear() {
    if (readBooksThisYear.isEmpty()) {
        return Optional.empty();
    }
    readBooksThisYear.sort(Comparator.comparing(Book::getRating));
    return Optional.of(readBooksThisYear.get(0));
}

@Test
void highestRatedBookExistsAndIsFound() {
    String actualTitle = yearStatistic.findMostLikedBookThisYear().get().getTitle();
    String expectedTitle = bookWithHighestRatingThisYear.getTitle();
    assertThat(actualTitle).isEqualTo(expectedTitle);
}

public Optional<Book> findMostLikedBookThisYear() {
    if (readBooksThisYear.isEmpty()) {
        return Optional.empty();
    }
    readBooksThisYear.sort(Comparator.comparing(Book::getRating));
    return Optional.of(readBooksThisYear.get(readBooksThisYear.size() - 1));
}


@Test
void goodreadsBookImportIsSavedWhenAuthorAndPredefinedShelfIsNotEmpty() {
    GoodreadsBookImport goodreadsBookImport = new GoodreadsBookImport();
    goodreadsBookImport.setTitle("Blink: The Power of Thinking Without Thinking");
    goodreadsBookImport.setAuthor("Malcolm Gladwell");
    goodreadsBookImport.setBookshelves("Reading");
    List<Book> savedBooks = importService.importGoodreadsBooks(Collections.singletonList(goodreadsBookImport));
    int expected = 1;
    assertThat(savedBooks.size()).isEqualTo(expected);
}


@Test
void testNoProgressMadeTowardsGoal() {
    assertThat(calculateProgressTowardsReadingGoal(5, 0)).isZero();
}

@Test
void testBooksToReadFromStartOfYear() {
    double booksToReadFromStartOfYear = booksToReadFromStartOfYear(26);
    assertThat(booksToReadFromStartOfYear).isEqualTo(0.5);
}


@Test
void notSaveANullTag() {
    assumeThat(tagService.count()).isZero();
    tagService.save(null);
    assertThat(tagService.count()).isZero();
}


@Test
void notSaveANullGoal() {
    assumeThat(goalService.count()).isOne();
    goalService.save(null);
    assertThat(goalService.count()).isOne();
}

public void save(ReadingGoal goal) {
    if (goal != null) {
        overwritePreviouslySetGoals(goal);
    }
}


@Test
void lowestRatedBookThisYearIsFound() {
    assertThat(yearStatistic.findLeastLikedBookThisYear().get().getTitle()).isEqualTo(bookWithLowestRatingThisYear.getTitle());
}

@Test
void highestRatedBookExistsAndIsFound() {
    assertThat(yearStatistic.findMostLikedBookThisYear().get().getTitle()).isEqualTo(bookWithHighestRatingThisYear.getTitle());
}


@Test
void getPredefinedShelfNamesCorrectlyAsStrings() {
    List<String> actualShelfNames = predefinedShelfUtils.getPredefinedShelfNamesAsStrings();
    List<String> expectedShelfNames = Stream.of(ShelfName.values()).map(Enum::toString).collect(Collectors.toList());
    assertSoftly(softly -> {
        softly.assertThat(actualShelfNames).hasSize(expectedShelfNames.size());
        softly.assertThat(expectedShelfNames).containsAll(actualShelfNames);
    });
}

public List<String> getPredefinedShelfNamesAsStrings() {
    return predefinedShelfService.findAllForLoggedInUser().stream().map(Shelf::getShelfName).collect(Collectors.toList());
}


@Test
void deveExtrairPrimeiraOcorrenciaDaTag() throws Exception {
    String xml = "<dados><item>Primeiro</item><item>Segundo</item></dados>";
    String resultado = XmlNfeUtil.getTag(xml, "item");
    assertEquals("<item>Primeiro</item>", resultado);
}

public static String getTag(String xml, String tag) throws NfeException {
    if (xml == null || xml.isEmpty()) {
        throw new NfeException("XML de entrada está vazio.");
    }
    try {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        Document doc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
        doc.getDocumentElement().normalize();
        XPath xPath = XPathFactory.newInstance().newXPath();
        Node node = (Node) xPath.evaluate("//*[local-name()='" + tag + "']", doc, XPathConstants.NODE);
        if (node == null) {
            throw new NfeException("Tag '" + tag + "' não encontrada no XML.");
        }
        return nodeToString(node);
    } catch (Exception e) {
        throw new NfeException("Erro ao extrair a tag '" + tag + "' do XML.\nErro: " + e.getMessage(), e);
    }
}

@Test
void deveExtrairTagCorretamente() throws Exception {
    String xml = "<evento><id>123</id><nome>Teste</nome></evento>";
    String resultado = XmlNfeUtil.getTag(xml, "id");
    assertEquals("<id>123</id>", resultado);
}


@Test
public void validarXmlNfeProc() {
    final String xml1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><nfeProc versao=\"4.00\" xmlns=\"http://www.portalfiscal.inf.br/nfe\"><NFe xmlns=\"http://www.portalfiscal.inf.br/nfe\"><infNFe versao=\"4.00\" Id=\"NFe52230309158456010159550010000731761567812345\"><ide><cUF>52</cUF><cNF>56781234</cNF><natOp>Venda de Comb. Congenere</natOp><mod>55</mod><serie>1</serie><nNF>73179</nNF><dhEmi>2023-03-21T11:30:57-03:00</dhEmi><dhSaiEnt>2023-03-21T11:30:00-03:00</dhSaiEnt><tpNF>1</tpNF><idDest>1</idDest><cMunFG>5220454</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>5</cDV><tpAmb>1</tpAmb><finNFe>1</finNFe><indFinal>0</indFinal><indPres>0</indPres><procEmi>0</procEmi><verProc>9.21.5</verProc></ide><emit><CNPJ>09158456000159</CNPJ><xNome>PHOENIX DIST DE COMBUSTIVEIS LTDA</xNome><xFant>PHOENIX DIST DE COMBUSTIVEIS LTDA</xFant><enderEmit><xLgr>AV TROPICAL S/NR QD. MOD, LT. 8, BLC. B, SL. 04</xLgr><nro>0</nro><xBairro>DISTR IND BRASIL CENTRAL</xBairro><cMun>5220454</cMun><xMun>SENADOR CANEDO</xMun><UF>GO</UF><CEP>75251722</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>6235124426</fone></enderEmit><IE>104282215</IE><CRT>3</CRT></emit><dest><CNPJ>08944957000360</CNPJ><xNome>PETROWORLD COMBUSTIVEIS S/A</xNome><enderDest><xLgr>AV. TROPICAL, LT. 05/06 A, SALA 25</xLgr><nro>0</nro><xBairro>DISTR INDL BR CENTRAL</xBairro><cMun>5220454</cMun><xMun>SENADOR CANEDO</xMun><UF>GO</UF><CEP>75251722</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>6236228077</fone></enderDest><indIEDest>1</indIEDest><IE>107680335</IE></dest><det nItem=\"1\"><prod><cProd>3301</cProd><cEAN>SEM GTIN</cEAN><xProd>OLEO DIESEL A S-500</xProd><NCM>27101921</NCM><CEST>0600600</CEST><CFOP>5655</CFOP><uCom>L</uCom><qCom>47878.0000</qCom><vUnCom>5.0139000000</vUnCom><vProd>240055.50</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>L</uTrib><qTrib>47878.0000</qTrib><vUnTrib>5.0139000000</vUnTrib><indTot>1</indTot><comb><cProdANP>420102004</cProdANP><descANP>OLEO DIESEL A S-500</descANP><qTemp>47878.0000</qTemp><UFCons>GO</UFCons></comb></prod><imposto><ICMS><ICMSST><orig>0</orig><CST>60</CST><vBCSTRet>0.00</vBCSTRet><vICMSSTRet>0.00</vICMSSTRet><vBCSTDest>322990.30</vBCSTDest><vICMSSTDest>45218.64</vICMSSTDest></ICMSST></ICMS><IPI><cEnq>999</cEnq><IPINT><CST>53</CST></IPINT></IPI><PIS><PISNT><CST>04</CST></PISNT></PIS><COFINS><COFINSNT><CST>04</CST></COFINSNT></COFINS></imposto><infAdProd>- Produto Retirado na DINAMICA TERMINAIS CANEDO S/A - AV TROPICAL SNR MODULO 06B - DISTR INDL BR CENTRAL - SENADOR CANEDO - GO - CNPJ: 33.047.181/0001-60 - Pedido = 5664401 - Qtde 20 graus: 47.821 litros; - Formulario seguranca:5664401; - Numero Boletim de Conformidade = 67/2023; - Base ICMS Origem = 322.990,30 - Valor = 45.218,64;</infAdProd></det><total><ICMSTot><vBC>0.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>240055.50</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>240055.50</vNF></ICMSTot></total><transp><modFrete>1</modFrete><transporta><CNPJ>09158456000310</CNPJ><xNome>PROPRIO</xNome><IE>107780828</IE><xEnder>AV TROPICAL MODULO 6B , BLOCO A SALA 16</xEnder><xMun>SENADOR CANEDO</xMun><UF>GO</UF></transporta><veicTransp><placa>AAA0000</placa><UF>GO</UF></veicTransp><vol><qVol>47878</qVol><marca>PHOENIX</marca><nVol>1</nVol><pesoL>40170.000</pesoL><pesoB>40170.000</pesoB><lacres><nLacre>0086434</nLacre></lacres></vol></transp><cobr><fat><nFat>073179</nFat><vOrig>240055.50</vOrig><vDesc>0.00</vDesc><vLiq>240055.50</vLiq></fat><dup><nDup>001</nDup><dVenc>2023-03-21</dVenc><vDup>240055.50</vDup></dup></cobr><pag><detPag><tPag>15</tPag><vPag>240055.50</vPag></detPag></pag><infAdic><infCpl>- Declaramos que os Produtos estao adequadamente acondicionados para as Normas de Carregamento, Descarregamento, Transbordo e Transporte, conforme Regulamentacao em Vigor. - Produto Retirado na DINAMICA TERMINAIS CANEDO S/A - AV TROPICAL SNR MODULO 06B - DISTR INDL BR CENTRAL - SENADOR CANEDO - GO - CNPJ: 33.047.181/0001-60 - Nome Motorista: VENDA CONGENERE PHOENIX - PETROWORLD - Placas: GO AAA-0000 - Pedido = 5664401 - Lacres = Azul 0086434; - Numero Boletim de Conformidade = 67/2023;</infCpl></infAdic><infRespTec><CNPJ>18406978000113</CNPJ><xContato>Ubaltino Faleiro</xContato><email>ubaltino@sistemainteligente.com</email><fone>62992887101</fone></infRespTec></infNFe><Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"></CanonicalizationMethod><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"></SignatureMethod><Reference URI=\"#NFe52230309158456000159550010000731791567812345\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"></Transform><Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"></Transform></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></DigestMethod><DigestValue>prmbdxKA/lyJ5RkjcliNkqY14nA=</DigestValue></Reference></SignedInfo><SignatureValue>YLElEQYakJPKh4oChHkSkfqFtRro2l2y3kN4wPiQbncv1EVFIn/PPrD/mi9GPF9U2g1SoHzYeNHR20QvhdyjcUF+DkgmHwT/SCvx89d0Q6ui3Gm5rggaCXw2MY7H99oxXMfT3OMKDCgCskiiHlTf9cCNF985BHs8y+j6yeDrZltAlrThq47xhET4I3xyBXlMlYWqvlnem/LN4eaVXAxCvkryzJCaFS+wtPJTEASWMz0mb1JT3VpYNvvcjFTH2PymY8x0wfvlP/bhlzPadiJ+BGKZ1971z3m+svJzX3wCZgw0Log/hjTyiRRQwqnwBbM+BHc8YDnuOY7ZqiHznbE/RA==</SignatureValue><KeyInfo><X509Data><X509Certificate>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</X509Certificate></X509Data></KeyInfo></Signature></NFe><protNFe versao=\"4.00\"><infProt><tpAmb>1</tpAmb><verAplic>GO4.0</verAplic><chNFe>52230309158456000159550010000731791567812345</chNFe><dhRecbto>2023-03-21T11:31:00-03:00</dhRecbto><nProt>152236109643154</nProt><digVal>prmbdxKA/lyJ5RkjcliNkqY14nA=</digVal><cStat>100</cStat><xMotivo>Autorizado o uso da NF-e</xMotivo></infProt></protNFe></nfeProc>";
    final String xml2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><nfeProc versao=\"4.00\" xmlns=\"http://www.portalfiscal.inf.br/nfe\"><NFe xmlns=\"http://www.portalfiscal.inf.br/nfe\"><infNFe versao=\"4.00\" Id=\"NFe43230489548223000180550100004329571146119231\"><ide><cUF>43</cUF><cNF>17611923</cNF><natOp>Venda</natOp><mod>55</mod><serie>10</serie><nNF>432957</nNF><dhEmi>2023-04-12T10:59:49-03:00</dhEmi><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4318705</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>1</cDV><tpAmb>1</tpAmb><finNFe>1</finNFe><indFinal>0</indFinal><indPres>9</indPres><indIntermed>0</indIntermed><procEmi>0</procEmi><verProc>4.00</verProc></ide><emit><CNPJ>89548523000180</CNPJ><xNome>ALISUL ALIMENTOS S.A. - SAO LEOPOLDO</xNome><xFant>ALISUL ALIMENTOS S.A.</xFant><enderEmit><xLgr>AV JOAO CARLOS HOHENDORFF</xLgr><nro>NR 900</nro><xBairro>ARROIO DA MANTEIGA</xBairro><cMun>4318705</cMun><xMun>SAO LEOPOLDO</xMun><UF>RS</UF><CEP>93135400</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>5121231400</fone></enderEmit><IE>1240047077</IE><CRT>3</CRT></emit><dest><CNPJ>18863437000114</CNPJ><xNome>EVERTON PAULO ALVES AGROPEC</xNome><enderDest><xLgr>RUA DJALMO HAACK</xLgr><nro>NR 748</nro><xBairro>ALEXANDRIA</xBairro><cMun>4314050</cMun><xMun>PAROBE</xMun><UF>RS</UF><CEP>95630000</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>5135434027</fone></enderDest><indIEDest>1</indIEDest><IE>2410071133</IE><email>EVERTON@EVERTON.COM.BR</email></dest><det nItem=\"1\"><prod><cProd>987E10.1</cProd><cEAN>7896803078888</cEAN><xProd>SAPECA PEQUENAS RACAS SC 10,1KG</xProd><NCM>23099010</NCM><CEST>2200100</CEST><EXTIPI>01</EXTIPI><CFOP>5401</CFOP><uCom>SC</uCom><qCom>20.0000</qCom><vUnCom>59.7663000000</vUnCom><vProd>1195.33</vProd><cEANTrib>7896803078888</cEANTrib><uTrib>SC</uTrib><qTrib>20.0000</qTrib><vUnTrib>59.7663000000</vUnTrib><vFrete>31.91</vFrete><indTot>1</indTot></prod><imposto><vTotTrib>453.65</vTotTrib><ICMS><ICMS10><orig>0</orig><CST>10</CST><modBC>1</modBC><vBC>1227.24</vBC><pICMS>17.0000</pICMS><vICMS>208.63</vICMS><modBCST>4</modBCST><pMVAST>72.2800</pMVAST><vBCST>2114.29</vBCST><pICMSST>17.0000</pICMSST><vICMSST>150.80</vICMSST></ICMS10></ICMS><IPI><cEnq>999</cEnq><IPITrib><CST>50</CST><vBC>1195.33</vBC><pIPI>0.0000</pIPI><vIPI>0.00</vIPI></IPITrib></IPI><PIS><PISAliq><CST>01</CST><vBC>1018.60</vBC><pPIS>1.6500</pPIS><vPIS>16.81</vPIS></PISAliq></PIS><COFINS><COFINSAliq><CST>01</CST><vBC>1018.60</vBC><pCOFINS>7.6000</pCOFINS><vCOFINS>77.41</vCOFINS></COFINSAliq></COFINS></imposto><infAdProd>Valor Aproximado dos Tributos - R$453.65.</infAdProd></det><det nItem=\"2\"><prod><cProd>986E10.1</cProd><cEAN>7896803077522</cEAN><xProd>SAPECA ADULTOS SC 10.1KG</xProd><NCM>23099010</NCM><CEST>2200100</CEST><EXTIPI>01</EXTIPI><CFOP>5401</CFOP><uCom>SC</uCom><qCom>15.0000</qCom><vUnCom>53.0301000000</vUnCom><vProd>795.45</vProd><cEANTrib>7896803077522</cEANTrib><uTrib>SC</uTrib><qTrib>15.0000</qTrib><vUnTrib>53.0301000000</vUnTrib><vFrete>23.93</vFrete><indTot>1</indTot></prod><imposto><vTotTrib>302.89</vTotTrib><ICMS><ICMS10><orig>0</orig><CST>10</CST><modBC>1</modBC><vBC>819.38</vBC><pICMS>17.0000</pICMS><vICMS>139.29</vICMS><modBCST>4</modBCST><pMVAST>72.2800</pMVAST><vBCST>1411.63</vBCST><pICMSST>17.0000</pICMSST><vICMSST>100.69</vICMSST></ICMS10></ICMS><IPI><cEnq>999</cEnq><IPITrib><CST>50</CST><vBC>795.45</vBC><pIPI>0.0000</pIPI><vIPI>0.00</vIPI></IPITrib></IPI><PIS><PISAliq><CST>01</CST><vBC>680.09</vBC><pPIS>1.6500</pPIS><vPIS>11.22</vPIS></PISAliq></PIS><COFINS><COFINSAliq><CST>01</CST><vBC>680.09</vBC><pCOFINS>7.6000</pCOFINS><vCOFINS>51.69</vCOFINS></COFINSAliq></COFINS></imposto><infAdProd>(1*) Valor Aproximado dos Tributos - R$302.89. Desconto Promocional - R$ 50,23.</infAdProd></det><det nItem=\"3\"><prod><cProd>985E10.1</cProd><cEAN>7896803077478</cEAN><xProd>SAPECA FILHOTES SC 10.1KG</xProd><NCM>23099010</NCM><CEST>2200100</CEST><EXTIPI>01</EXTIPI><CFOP>5401</CFOP><uCom>SC</uCom><qCom>15.0000</qCom><vUnCom>63.8116000000</vUnCom><vProd>957.17</vProd><cEANTrib>7896803077478</cEANTrib><uTrib>SC</uTrib><qTrib>15.0000</qTrib><vUnTrib>63.8116000000</vUnTrib><vFrete>23.93</vFrete><indTot>1</indTot></prod><imposto><vTotTrib>362.67</vTotTrib><ICMS><ICMS10><orig>0</orig><CST>10</CST><modBC>1</modBC><vBC>981.10</vBC><pICMS>17.0000</pICMS><vICMS>166.79</vICMS><modBCST>4</modBCST><pMVAST>72.2800</pMVAST><vBCST>1690.24</vBCST><pICMSST>17.0000</pICMSST><vICMSST>120.55</vICMSST></ICMS10></ICMS><IPI><cEnq>999</cEnq><IPITrib><CST>50</CST><vBC>957.17</vBC><pIPI>0.0000</pIPI><vIPI>0.00</vIPI></IPITrib></IPI><PIS><PISAliq><CST>01</CST><vBC>814.31</vBC><pPIS>1.6500</pPIS><vPIS>13.44</vPIS></PISAliq></PIS><COFINS><COFINSAliq><CST>01</CST><vBC>814.31</vBC><pCOFINS>7.6000</pCOFINS><vCOFINS>61.89</vCOFINS></COFINSAliq></COFINS></imposto><infAdProd>(1*) Valor Aproximado dos Tributos - R$362.67.</infAdProd></det><total><ICMSTot><vBC>3027.72</vBC><vICMS>514.71</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>5216.16</vBCST><vST>372.04</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>2947.95</vProd><vFrete>79.77</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>41.47</vPIS><vCOFINS>190.99</vCOFINS><vOutro>0.00</vOutro><vNF>3399.76</vNF><vTotTrib>1119.21</vTotTrib></ICMSTot></total><transp><modFrete>0</modFrete><transporta><CNPJ>24792638000170</CNPJ><xNome>TRANSPORTADORA DE CARGAS SAO JOAO LTDA</xNome><IE>0780107713</IE><xEnder>EST. MAURICIO CARDOSO,SN - KM 08 - PASSO DA SERRA</xEnder><xMun>MONTENEGRO</xMun><UF>RS</UF></transporta><veicTransp><placa>IKB0F64</placa><UF>RS</UF></veicTransp><vol><qVol>50</qVol><esp>SC</esp><marca>SUPRA</marca><pesoL>505.000</pesoL><pesoB>511.300</pesoB></vol></transp><cobr><fat><nFat>432957</nFat><vOrig>3399.76</vOrig><vDesc>0.00</vDesc><vLiq>3399.76</vLiq></fat><dup><nDup>001</nDup><dVenc>2023-04-19</dVenc><vDup>849.97</vDup></dup><dup><nDup>002</nDup><dVenc>2023-04-26</dVenc><vDup>849.93</vDup></dup><dup><nDup>003</nDup><dVenc>2023-05-03</dVenc><vDup>849.93</vDup></dup><dup><nDup>004</nDup><dVenc>2023-05-10</dVenc><vDup>849.93</vDup></dup></cobr><pag><detPag><indPag>1</indPag><tPag>15</tPag><vPag>3399.76</vPag></detPag></pag><infAdic><infCpl>VENDA PET - ICMS RETIDO DE ACORDO COM PROTOCOLO 26/2004 - COMBINADO COM LIVRO III, ART. 177 - RICMS/RS // OPERACAO SUJEITA A NAO-INCIDENCIA DO IPI, PARA AS EMBALAGENS SUPERIORES A 10KG, EM FACE DE DECISAO TRANSITADA EM JULGADO NOS AUTOS DA ACAO DECLARATORIA NUMERO 2002.71.08.004673-4/RS / IPI NAO CALCULADO SOBRE O VALOR DO FRETE EM RAZAO DA DECISAO MANDADO DE SEGURANCA n. 500305287.2017.4.04.7108/RS. (1*) - Produto isento de registro junto ao Ministerio da Agricultura, Pecuaria e Abastecimento.</infCpl></infAdic></infNFe><Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#NFe43230489548523000180550100004329571176119231\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>5Pn0xv9AzYUyaW+Sd/sKNgTOdW4=</DigestValue></Reference></SignedInfo><SignatureValue>Zb9fmJW+hkG2he/NJ2lV5T9EyupVeY1pcqPpQxtvV/967RYcOn16gblGtn0IsG9sEuWGVXu6BR6dK2ITmuzAu/1+/gGEAFVt8KRvNOmT/4GlVU2YMGkix+dw1YIQ87/fexyBt0CrcRsO45R4dbFC031G7RgRxXWGh2u4g1GigHwQGsff4ayDjlHNfWRY2wrFy853Ku2glFI7Zy920Mu0EDvR7LrAx+fHp/WQKPrvty0t9Z1mWYOvGFldRVOfOPIhZHMwawQIr6NHALrhHMFi+0c8ZAYBqP4z/5F3ckjHQjQ+XeBCdHceX7subS89pk+UJMUxses2OGYH3lrRn2ddZg==</SignatureValue><KeyInfo><X509Data><X509Certificate>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</X509Certificate></X509Data></KeyInfo></Signature></NFe><protNFe versao=\"4.00\"><infProt Id=\"ID143230080568074\"><tpAmb>1</tpAmb><verAplic>RS202303091530</verAplic><chNFe>43230489548523000180550100004329571176119231</chNFe><dhRecbto>2023-04-12T10:59:51-03:00</dhRecbto><nProt>143230080568074</nProt><digVal>5Pn0xv9AzYUyaW+Sd/sKNgTOdW4=</digVal><cStat>100</cStat><xMotivo>Autorizado o uso da NF-e</xMotivo></infProt></protNFe></nfeProc>";
    final String xml3 = "<nfeProc versao=\"4.00\" xmlns=\"http://www.portalfiscal.inf.br/nfe\"><NFe xmlns=\"http://www.portalfiscal.inf.br/nfe\"><infNFe Id=\"NFe52230332047181000160550010100671841001179123\" versao=\"4.00\"><ide><cUF>52</cUF><cNF>00117912</cNF><natOp>RETORNO COMB REC P/ ARMAZENAGEM</natOp><mod>55</mod><serie>1</serie><nNF>67184</nNF><dhEmi>2023-03-31T13:10:48-03:00</dhEmi><dhSaiEnt>2023-03-31T13:10:48-03:00</dhSaiEnt><tpNF>1</tpNF><idDest>1</idDest><cMunFG>5220454</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>2</cDV><tpAmb>1</tpAmb><finNFe>1</finNFe><indFinal>0</indFinal><indPres>1</indPres><indIntermed>0</indIntermed><procEmi>0</procEmi><verProc>petrow.com.br</verProc><NFref><refNFe>52230344297367000275550010000091821801533385</refNFe></NFref><NFref><refNFe>52230344297367000275550010000092241698760819</refNFe></NFref></ide><emit><CNPJ>33047181000160</CNPJ><xNome>DINAMICA TERMINAIS CANEDO S/A</xNome><xFant>DTC</xFant><enderEmit><xLgr>AVENIDA TROPICAL</xLgr><nro>0</nro><xCpl>QUADRAMODULO 6-B</xCpl><xBairro>DISTRITO INDUSTRIAL BRASIL CENTRAL</xBairro><cMun>5220454</cMun><xMun>SENADOR CANEDO</xMun><UF>GO</UF><CEP>75251722</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>6235122932</fone></enderEmit><IE>107550547</IE><CRT>3</CRT></emit><dest><CNPJ>44297367000275</CNPJ><xNome>WK PRODUTOS DE PETROLEO LTDA</xNome><enderDest><xLgr>AVENIDA TROPICAL</xLgr><nro>0</nro><xBairro>DISTRITO INDUSTRIAL BRASIL CENTRAL</xBairro><cMun>5220454</cMun><xMun>SENADOR CANEDO</xMun><UF>GO</UF><CEP>75251722</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>6233071595</fone></enderDest><indIEDest>1</indIEDest><IE>109208153</IE><email>wkcombustiveis@gropowk.net.br</email></dest><det nItem=\"1\"><prod><cProd>58/S10 A</cProd><cEAN>SEM GTIN</cEAN><xProd>S10 A 420105001</xProd><NCM>27101921</NCM><CFOP>5664</CFOP><uCom>L</uCom><qCom>2249</qCom><vUnCom>4.9218008004</vUnCom><vProd>11069.13</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>L</uTrib><qTrib>2249</qTrib><vUnTrib>4.9218008004</vUnTrib><indTot>1</indTot><comb><cProdANP>420105001</cProdANP><descANP>OLEO DIESEL A S10</descANP><UFCons>GO</UFCons></comb></prod><imposto><ICMS><ICMS40><orig>0</orig><CST>41</CST></ICMS40></ICMS><IPI><cEnq>004</cEnq><IPINT><CST>54</CST></IPINT></IPI><PIS><PISNT><CST>09</CST></PISNT></PIS><COFINS><COFINSNT><CST>09</CST></COFINSNT></COFINS></imposto><infAdProd>NFE52230344297367000275550010000092241698760819 9.224/1 28/03/2023 TA: 21,0 DA: 0,8345 D20: 0,8352 FC: 0,9992</infAdProd></det><det nItem=\"2\"><prod><cProd>68/B100</cProd><cEAN>SEM GTIN</cEAN><xProd>BIODIESEL 820101001</xProd><NCM>38260000</NCM><CFOP>5664</CFOP><uCom>L</uCom><qCom>250</qCom><vUnCom>4.6382400000</vUnCom><vProd>1159.56</vProd><cEANTrib>SEM GTIN</cEANTrib><uTrib>L</uTrib><qTrib>250</qTrib><vUnTrib>4.6382400000</vUnTrib><indTot>1</indTot><comb><cProdANP>820101001</cProdANP><descANP>BIODIESEL B100</descANP><UFCons>GO</UFCons></comb></prod><imposto><ICMS><ICMS40><orig>0</orig><CST>41</CST></ICMS40></ICMS><IPI><cEnq>004</cEnq><IPINT><CST>54</CST></IPINT></IPI><PIS><PISNT><CST>09</CST></PISNT></PIS><COFINS><COFINSNT><CST>09</CST></COFINSNT></COFINS></imposto><infAdProd>NFE52230344297367000275550010000091821801533385 9.182/1 27/03/2023 TA: 21,0 DA: 0,8776 D20: 0,8781 FC: 0,9992</infAdProd></det><total><ICMSTot><vBC>0.00</vBC><vICMS>0.00</vICMS><vICMSDeson>0.00</vICMSDeson><vFCPUFDest>0.00</vFCPUFDest><vICMSUFDest>0.00</vICMSUFDest><vICMSUFRemet>0.00</vICMSUFRemet><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>12228.69</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.00</vCOFINS><vOutro>0.00</vOutro><vNF>12228.69</vNF><vTotTrib>0.00</vTotTrib></ICMSTot></total><transp><modFrete>4</modFrete><transporta><CNPJ>44297367000275</CNPJ><xNome>WK PRODUTOS DE PETROLEO LTDA</xNome><IE>109208153</IE><xEnder>AVENIDA TROPICAL, S/N  DISTRITO INDUSTRIAL BRASIL CENTRAL</xEnder><xMun>SENADOR CANEDO</xMun><UF>GO</UF></transporta><veicTransp><placa>FRI3C57</placa></veicTransp><vol><qVol>2499</qVol><esp>GRANEL</esp><marca>DTC</marca><pesoL>2097.890</pesoL><pesoB>2097.890</pesoB></vol></transp><pag><detPag><indPag>0</indPag><tPag>90</tPag><vPag>0.00</vPag></detPag></pag><infAdic><infCpl>DECLARAMOS QUE OS PRODUTOS ESTAO ADEQUADAMENTE EMBALADOS/ACONDICIONADOS P/ SUPORTAR OS RISCOS DE CARREGAMENTO, DESCARREGAMENTO, TRANSBORDO E TRANSPORTE, CONFORME REGULAMENTACAO EM VIGOR. NAO INCIDENCIA DE ICMS, CONF. DEC. 4852/97, ART. 79, INC. I, ALINEA I RCTE-SEFAZ-GO.|002.515-03 FRI3C57 ERONICE 31/03/23</infCpl></infAdic><infRespTec><CNPJ>25337354000157</CNPJ><xContato>EVERTON ANTONIO RAMOS</xContato><email>everton@txfuel.com.br</email><fone>41991341456</fone></infRespTec></infNFe><Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#NFe52230333047181000160550010000671841001179122\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>Gl2d7APkqcG4yWb3DNE8/biib7I=</DigestValue></Reference></SignedInfo><SignatureValue>M5oNfVMhNTv7iGf9XZ78IQHGPKqluwzm02aF46Lu/TNSEkmDf/f++Hq3OOkzuHoKnIpViGYn7WZ7c5fGg8xGa4HEd/dU20bjDIrGmLX5VjknNeUD+0Vrv5gnokDl/T3GCa029R4jkAaaFBlD56uKdvAuc4UdoY7naMfXKyshfrTK3ojQIf5rh4lk8ocn73Ak4mfkvmIX4UhHQ8GDACRBTzYogvRiYdNprTq/U24h+xAjkuG2TMasZoit8sEKlfPVYVKIjeLgrVMk3r1lqLhHez419H6ULpAxi2zHLpIO/O8TyEDHbwpuhcjzdKWciEBcX9X1tqNi9qTxa/WX1/eWWw==</SignatureValue><KeyInfo><X509Data><X509Certificate>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</X509Certificate></X509Data></KeyInfo></Signature></NFe><protNFe versao=\"4.00\"><infProt Id=\"NFe00\"><tpAmb>1</tpAmb><verAplic>GO4.0</verAplic><chNFe>52230333047181000160550010000671841001179122</chNFe><dhRecbto>2023-03-31T13:10:51-03:00</dhRecbto><nProt>152236143080702</nProt><digVal>Gl2d7APkqcG4yWb3DNE8/biib7I=</digVal><cStat>100</cStat><xMotivo>Autorizado o uso da NF-e</xMotivo></infProt></protNFe></nfeProc>";
    final Validar validar = new Validar();
    assertEquals(true, validar.isValidXml("schemas", xml1, ServicosEnum.PROC));
    assertEquals(true, validar.isValidXml("schemas", xml2, ServicosEnum.PROC));
    assertEquals(true, validar.isValidXml("schemas", xml3, ServicosEnum.PROC));
}

public boolean isValidXml(String pastaSchemas, String xml, ServicosEnum servico) {
    return isValidXml(pastaSchemas + System.getProperty("file.separator") + servico.getXsd(), xml);
}