1

I have a java web application and I use Liquibase for database migrations.

I want my application to not require Internet access, but if liquibase requires a specific version of a schema file that is not included, it will try to download it.

So I want to create a unit test where the application doesn't have Internet access which will fail if the application attempts to access the Internet.

Is this feasible and are there better solutions ?

Edit: I've added the stack trace of when the app doesn't have Internet access and also doesn't have the XSD file included.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.ChangeLogParseException: liquibase.exception.SetupException: Error parsing line 6 column 243 of db/changelog/900-scratch.xml: schema_reference.4: Failed to read schema document 'http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:307) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744) [spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391) [spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) [spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204) [spring-boot-2.1.9.RELEASE.jar:2.1.9.RELEASE]
        at de.it4process.medorus.MedorusServerApplication.main(MedorusServerApplication.java:28) [classes/:na]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_232]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_232]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_232]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_232]
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.1.9.RELEASE.jar:2.1.9.RELEASE]
Caused by: liquibase.exception.ChangeLogParseException: liquibase.exception.SetupException: Error parsing line 6 column 243 of db/changelog/900-scratch.xml: schema_reference.4: Failed to read schema document 'http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
        at liquibase.parser.core.xml.AbstractChangeLogParser.parse(AbstractChangeLogParser.java:25) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.Liquibase.getDatabaseChangeLog(Liquibase.java:217) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.Liquibase.update(Liquibase.java:190) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.Liquibase.update(Liquibase.java:179) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.integration.spring.SpringLiquibase.performUpdate(SpringLiquibase.java:353) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:305) ~[liquibase-core-3.6.3.jar:na]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
        ... 23 common frames omitted
Caused by: liquibase.exception.SetupException: Error parsing line 6 column 243 of db/changelog/900-scratch.xml: schema_reference.4: Failed to read schema document 'http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
        at liquibase.changelog.DatabaseChangeLog.handleChildNode(DatabaseChangeLog.java:336) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.changelog.DatabaseChangeLog.load(DatabaseChangeLog.java:293) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.parser.core.xml.AbstractChangeLogParser.parse(AbstractChangeLogParser.java:23) ~[liquibase-core-3.6.3.jar:na]
        ... 30 common frames omitted
Caused by: liquibase.exception.ChangeLogParseException: Error parsing line 6 column 243 of db/changelog/900-scratch.xml: schema_reference.4: Failed to read schema document 'http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
        at liquibase.parser.core.xml.XMLChangeLogSAXParser.parseToNode(XMLChangeLogSAXParser.java:120) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.parser.core.xml.AbstractChangeLogParser.parse(AbstractChangeLogParser.java:15) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.changelog.DatabaseChangeLog.include(DatabaseChangeLog.java:525) ~[liquibase-core-3.6.3.jar:na]
        at liquibase.changelog.DatabaseChangeLog.handleChildNode(DatabaseChangeLog.java:334) ~[liquibase-core-3.6.3.jar:na]
        ... 32 common frames omitted
Caused by: org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document 'http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
        at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.warning(ErrorHandlerWrapper.java:99) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:392) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:306) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaErr(XSDHandler.java:4158) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaWarning(XSDHandler.java:4149) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument1(XSDHandler.java:2491) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument(XSDHandler.java:2193) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.parseSchema(XSDHandler.java:578) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadSchema(XMLSchemaLoader.java:610) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.findSchemaGrammar(XMLSchemaValidator.java:2447) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1768) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.startElement(XMLSchemaValidator.java:741) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:374) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl$NSContentDriver.scanRootElementHook(XMLNSDocumentScannerImpl.java:613) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3132) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:852) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643) ~[na:1.8.0_232]
        at liquibase.parser.core.xml.XMLChangeLogSAXParser.parseToNode(XMLChangeLogSAXParser.java:112) ~[liquibase-core-3.6.3.jar:na]
        ... 35 common frames omitted
Caused by: java.net.UnknownHostException: www.liquibase.org
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[na:1.8.0_232]
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_232]
        at java.net.Socket.connect(Socket.java:607) ~[na:1.8.0_232]
        at java.net.Socket.connect(Socket.java:556) ~[na:1.8.0_232]
        at sun.net.NetworkClient.doConnect(NetworkClient.java:180) ~[na:1.8.0_232]
        at sun.net.www.http.HttpClient.openServer(HttpClient.java:463) ~[na:1.8.0_232]
        at sun.net.www.http.HttpClient.openServer(HttpClient.java:558) ~[na:1.8.0_232]
        at sun.net.www.http.HttpClient.<init>(HttpClient.java:242) ~[na:1.8.0_232]
        at sun.net.www.http.HttpClient.New(HttpClient.java:339) ~[na:1.8.0_232]
        at sun.net.www.http.HttpClient.New(HttpClient.java:357) ~[na:1.8.0_232]
        at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1226) ~[na:1.8.0_232]
        at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1162) ~[na:1.8.0_232]
        at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1056) ~[na:1.8.0_232]
        at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:990) ~[na:1.8.0_232]
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1570) ~[na:1.8.0_232]
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:647) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(XMLVersionDetector.java:148) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:583) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:686) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaDOMParser.parse(SchemaDOMParser.java:530) ~[na:1.8.0_232]
        at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument(XSDHandler.java:2181) ~[na:1.8.0_232]
        ... 53 common frames omitted
oussema
  • 322
  • 4
  • 15
  • Do you have any code of the unit test we could start from? Can you mock the object that tries to download the files? If so, you can tell it to not run the method. – Wesley De Keirsmaeker Mar 03 '20 at 13:09
  • No I'm just starting, I only have one test that tests the migrations on an empty database. – oussema Mar 03 '20 at 13:16

2 Answers2

2

Yes, it's feasible, although the idea of such unit test is not quite clear for me. But to answer your question I'd suggest the following:

  • mock the method which is responsible for schema file downloading;
  • make it throw something like ConnectException, so download process will result in exception;
  • implement your test's logic based on the result of downloading;

Some framework like Mockito will be helpful.

htshame
  • 6,599
  • 5
  • 36
  • 56
  • Thank you for your suggestion, I looked at the liquibase code and it seems that a dependency is responsible for parsing the xml changelog and therefore the downloading of the schema file: **org.xml.sax**. I'm not sure how to use mockito with it. – oussema Mar 03 '20 at 16:44
  • Do you really need to test this scenario? Because it seems kind of odd – htshame Mar 03 '20 at 16:48
0

Typically web applications that use Liquibase will include the changelog file in their jar file or whatever other artifact contains the runnable code. That would be a better solution to your issue. That also ensures that the code reading/writing the database and the schema in the database are in sync.

SteveDonie
  • 8,700
  • 3
  • 43
  • 43
  • I'm not sure I understood your comment, my app includes the changelog files, my problem is that if a changelog file requires a .xsd file ( such as _http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd_ ) that is not included in the liquibase package, it will be download it from the Internet. – oussema Mar 03 '20 at 17:00
  • Ahh - when you said 'schema file' I assumed you meant changelog rather than XSD. Liquibase includes all the relevant xsd files in the liquibase jar file, so it shouldn't need to download those. If there is a case where that is not true, it is a bug. – SteveDonie Mar 03 '20 at 17:18
  • 1
    I'm pretty sure that the xsd file is being downloaded, when I change _liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd_ to _liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd_ and have Internet access when I run the app, everything is fine, but when I don't have Internet access, the migrations fail. I'm sorry I can't provide the stack trace now, but it's basically the liquibase falling to parse the changelog file, ..., caused the org.xml.sax parser throwing an exception, ..., caused by an unknown host exception: liquibase.org. – oussema Mar 03 '20 at 18:32
  • OK, that sounds like a bug. What version of Liquibase are you using? Also, you can't use an xsd in your changelog that references a higher version than the software itself. So if you are using liquibase 3.6.x, you can't reference the 3.8 xsd. – SteveDonie Mar 04 '20 at 16:12
  • I'm using spring boot 2.1 and liquibase 3.6. I know that if I'm using the 3.6 version I shouldn't reference the 3.8.xsd, that's why I'm trying to figure out a unit test that can fail if that happens. – oussema Mar 05 '20 at 14:18